2018-09-16

2: Git's Checkout Behavior

<The previous article in this series | The table of contents of this series | The next article in this series>

Whether it is rational or not, whatever the motive is, Git's checkout behaves like this.

Topics


About: Git

The table of contents of this article


Starting Context


  • The reader has knowledge on the whole picture of Git.
  • The reader has knowledge on basic operations (staging, committing, checking out, etc.) of Git.

Target Context


  • The reader will understand how Git's checkouts behave.

Orientation


Hypothesizer 7
First, let us remember that there are two kinds of checkouts: 'commit checkout' and 'files checkout'; they are conceptually very different.

Any repository at any time (except when it has no commit yet) has a single current commit (which is actually 'HEAD'), and any commit checkout sets the current commit to be the specified commit (note that any branch is a pointer to a commit). The current commit is the commit on which we work at the time.

The commit checkout operation, basically, also sets up the working tree so that the working tree has the registered files of the new current commit (which is natural because when we begin to work on the new current commit, we usually first want to have the authentic files of the new current commit at hand, right?). But "basically"? . . . Yes, only basically: it is more complicated, which is the main issue here.

On the other hand, any files checkout creates or replaces some files in the working tree in the current commit (with the current commit keeping being the same commit), using the contents of the files registered in the specified commit (which can be the current commit or another commit).

There is an important thing to note though, as will be discussed in the main body.


Main Body


1: Commit Checkouts' Unfathomable Behavior


Hypothesizer 7
'Orientation' should have clarified what 'commit checkout' basically is, but its behavior is unfathomable (in the meaning that its guiding principle is incomprehensible, at least to me, and its rationality is questionable, at least to me). However, I can list how it behaves as it does.

Note that when any commit checkout changes the current commit from a commit to another commit, I call the former commit and the latter commit 'the previous current commit' and 'the new current commit', respectively.

First, when the previous current commit is clean (meaning that all the changes have been committed), there is nothing to be specifically mentioned.

Second, when the previous current commit had any uncommitted (whether not staged or staged) change, the commit checkout behaves in a complicated manner.

In fact, the commit checkout does not always tries to fill the staging area and the working tree with the registered files of the new current commit, but sometimes tries to carry over the uncommitted changes that were being done to the previous current commit, into the new current commit.

Honestly, I do not understand the rationality of the latter try: the changes were naturally (at least for me) for the previous current commit (why will I ever make the changes to the previous current commit if they are for the new current commit?), and I do not want the changes for the previous current commit to be carried over into the new current commit: most certainly, I have just forgotten to commit the changes to the previous current commit and Git's reminding me the forgetfulness would be nice.

Besides, the criterion on when Git opts to do the latter try is incomprehensible to me.

In fact, these are the tests I have done in order to understand Git's commit checkout behavior. To explain the descriptions below, for example, in the test, 'A-1', the previous current commit does not have the concerned file been committed ('none' means that there is no file there), has the concerned file been staged with the contents of 'ccc', and has the concerned file in the working tree with the contents of 'ccc'; the new current commit does not have the concerned file been committed (as the new current commit exists only in the repository before the checkout, there is no staged state or working tree state for it at that time); the result message of the checkout is "A ccc.txt", the new current commit has the concerned file been staged with the contents of 'ccc', and has the concerned file in the working tree with the contents of 'ccc', after the checkout. 'Message''s 'none' does not mean that there is no message at all, but there is no message to be specifically mentioned. Any message , "A . . .", means that the change as an added file has been carried over into the new current commit; any message , "M . . .", means that the change as a modified file has been carried over into the new current commit; any message , "D . . .", means that the change as a removed file has been carried over into the new current commit; any error means that Git tried to restore the registered state of the new current commit and found out that the restoration would destroy the changes that had been done to the previous current commit.

# Tests for an Added File Start

Test Number: A-1
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> ccc
New Current Commit : Committed -> none
--> Checkout -->
Message : A ccc.txt
Staged : ccc
Working : ccc

Test Number: A-2
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> cccc
New Current Commit : Committed -> none
--> Checkout -->
Message : A ccc.txt
Staged : ccc
Working : cccc

Test Number: A-3
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> none
New Current Commit : Committed -> none
--> Checkout -->
Message : none
Staged : ccc
Working : none
* Although the change has been carried over into the new current commit, there is no message.

Test Number: A-4
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> ccc
New Current Commit : Committed -> ccc
--> Checkout -->
Message : none
Staged : ccc
Working : ccc

Test Number: A-5
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> cccc
New Current Commit : Committed -> ccc
--> Checkout -->
Message : M ccc.txt
Staged : ccc
Working : cccc

Test Number: A-6
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> none
New Current Commit : Committed -> ccc
--> Checkout -->
Message : D ccc.txt
Staged : ccc
Working : none

Test Number: A-7
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> ccc
New Current Commit : Committed -> cccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: A-8
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> cccc
New Current Commit : Committed -> cccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: A-9
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> none
New Current Commit : Committed -> cccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: A-10
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> ccc
New Current Commit : Committed -> ccccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: A-11
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> cccc
New Current Commit : Committed -> ccccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: A-12
Previous Current Commit: Committed -> none, Staged -> ccc, Working -> none
New Current Commit : Committed -> ccccc
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

# Tests for an Added File End

# Tests for a Modified File Start

Test Number: M-1
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> aaaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-2
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-3
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-4
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-5
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> none
New Current Commit : Committed -> none
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-6
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> aaaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : M aaa.txt
Staged : aaa
Working : aaaa

Test Number: M-7
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : M aaa.txt
Staged : aaaa
Working : aaaa

Test Number: M-8
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : M aaa.txt
Staged : aaaa
Working : aaaaa

Test Number: M-9
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : M aaa.txt
Staged : aaaa
Working : aaa

Test Number: M-10
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> none
New Current Commit : Committed -> aaa
--> Checkout -->
Message : D aaa.txt
Staged : aaaa
Working : none
* Although the modification has been carried over as staged into the new current commit, the message is on the deletion in the working tree.

Test Number: M-11
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> aaaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-12
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : none
Staged : aaaa
Working : aaaa
* The staging for the previous current commit has been silently lost.

Test Number: M-13
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : M aaa.txt
Staged : aaaa
Working : aaaaa

Test Number: M-14
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : M aaa.txt
Staged : aaaa
Working : aaa

Test Number: M-15
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> none
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : D aaa.txt
Staged : aaaa
Working : none

Test Number: M-16
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> aaaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-17
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-18
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaaaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-19
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> aaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: M-20
Previous Current Commit: Committed -> aaa, Staged -> aaaa, Working -> none
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

# Tests for a Modified File End

# Tests for Removed a File Start

Test Number: R-1
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> none
New Current Commit : Committed -> none
--> Checkout -->
Message : none
Staged : none
Working : none

Test Number: R-2
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: The following untracked working tree files would be removed by checkout:
Staged : n/a
Working : n/a

Test Number: R-3
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> none
New Current Commit : Committed -> none
--> Checkout -->
Message : none
Staged : none
Working : none
* The staging for the previous current commit has been silently lost.

Test Number: R-4
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaaa
New Current Commit : Committed -> none
--> Checkout -->
Message : error: The following untracked working tree files would be removed by checkout:
Staged : n/a
Working : n/a

Test Number: R-5
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> none
New Current Commit : Committed -> aaa
--> Checkout -->
Message : D aaa.txt
Staged : aaa
Working : none

Test Number: R-6
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : D aaa.txt
Staged : none
Working : aaa

Test Number: R-7
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> none
New Current Commit : Committed -> aaa
--> Checkout -->
Message : D aaa.txt
Staged : none
Working : none

Test Number: R-8
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaaa
New Current Commit : Committed -> aaa
--> Checkout -->
Message : D aaa.txt
Staged : none
Working : aaaa

Test Number: R-9
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> none
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : none
Staged : aaaa
Working : aaaa

Test Number: R-10
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: R-11
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> none
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: R-12
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaaa
New Current Commit : Committed -> aaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: R-13
Previous Current Commit: Committed -> aaa, Staged -> aaa, Working -> none
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : none
Staged : aaaaa
Working : aaaaa

Test Number: R-14
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: R-15
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> none
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

Test Number: R-16
Previous Current Commit: Committed -> aaa, Staged -> none, Working -> aaaa
New Current Commit : Committed -> aaaaa
--> Checkout -->
Message : error: Your local changes to the following files would be overwritten by checkout:
Staged : n/a
Working : n/a

# Tests for a Removed File End

Hmm . . ., some results are weird, I would say.

First, Git sometimes tries to carry over the changes and sometimes tries to just restore the registered state of the new current commit, but on what criterion?

Well, I had built a hypothesis that only when the file contents in the new current commit equaled the file contents in the previous current commit repository (supposing 'none' equaled 'none'), Git would carry over the changes, but some results contradict the hypothesis: 'A-5', 'A-6', 'M-13', 'M-14', and 'M-15'. And I had built another hypothesis that only when the file contents in the new current commit equaled the file contents in the previous current commit repository or the file contents in the the previous current commit staging area (supposing 'none' equaled 'none'), Git would carry over the changes, but some results contradict the hypothesis: 'R2' and 'R4'. . . . Does removing just have a different criterion? . . . Hmm, anyway, whatever the criterion is, the behavior does not make sense to me.

Besides, some messages or non-existence of them seem unreasonable to me. In fact, why does not 'A-3' give the message that the change has been carried over as staged? If the user does not notice the fact, the file can be committed to the new current commit unintentionally . . .. And in each of 'M-12' and 'R-3', the staging for the previous current commit just has vanished without any notice (certainly, the contents itself remains in the staging area in the new current commit, but the fact of its being staged for the previous current commit just has vanished). And in 'M-10', the "D" message is confusing because the modification, not the deletion, is staged for the new current commit: the next commit to the new current commit will modify, not remove, the file.

Honestly, I do not understand why Git has to have such complicated behavior. I think, it should just give a warning and block the checkout when there is any uncommitted change in the previous current commit, unless the '-f' flag is specified.

Should I just always use the '-f' flag? . . .You know, I need the warning, which using the '-f' flag does not give.


2: Files Checkout Behavior to Beware of


Hypothesizer 7
Any files checkout doesn't just place the files in the working tree, but also automatically stages the files.

Hmm, that is also behavior I do not favor. . . . I almost always do not check out a file in any conviction that I will certainly commit the file, but I first examine the file (typically by rebuilding the project and doing some tests) and then decide to commit it. The file's being automatically staged is an annoyance: I have to take caution not to unintentionally commit such files.

Should I use rather 'show'? Just showing doesn't let me do tests. Should I redirect the shown result into the file? . . . Well, if I have to do so, I would have to do so, although that is not particularly desirable (I have to specify the file path of the redirection and I cannot check out multiple files at once) . . .


3: The Conclusion and Beyond


Hypothesizer 7
Now, I seem to understand how Git's checkouts behave (although not their guiding principle).

In short, in any commit checkout, Git sometimes kindly (really?) carries over uncommitted changes meant for the previous current commit, into the new current commit, and in any files checkout, Git kindly (really?) stages the files before the user examines the files.

I have investigated that behavior because that influences (unfavorably) my scheme of storing and restoring file modification times. For example, as changes in the working tree are carried over from the previous current commit into the new current commit, I cannot just restore the file modification times that has been registered in the new current commit.

Hmm, honestly, the more I learn Git, the more I dislike it . . . I swear that I began to learn Git because I had high expectation of it, but its way of thinking doesn't fit me well . . .


References


<The previous article in this series | The table of contents of this series | The next article in this series>