I'm observing something that is not consistent with what I know about the git checkout
command. While on a branch other than master
, I make some modifications to a tracked file; without staging and committing those changes, I run
git checkout master
Git complies without batting an eyelid; but what is even more surprising is that all the modifications I made on that branch are still present! Here is an example reproducing the situation:
mkdir myrepo
cd myrepo
git init
touch README # create a new file
git add .
git commit -m "initial commit"
git checkout -b new-branch
echo "foo" >> README
git checkout master
At this stage, the README
file in my working tree contains the foo
line, even though I added it while on the other branch (new-branch
). I was expecting README
to be empty, like the version recorded in the tip of master
. Why is the foo
line still there after checking out master
?
Until a change is staged (using
git add
), it just exists in the working directory - it is not tracked by git, and therefore will exist in any branch you checkout.At first sight, your question seemed pretty uninteresting, but it made me realize that
git checkout
is not as simple an operation as it seems. Thanks for asking it:)
As I understand it, your question is: why are the uncommitted changes, i.e. the added
foo
line, still present in the working tree after checking outmaster
? If you look up thegit-checkout
man page, you'll find the following description:However, this description seems to contradict what's happening in your example. You're not the first one to be confused by it; see this discussion. In it, Junio Hamano, the maintainer of Git, clarifies what
git checkout <commit-ish>
does in the presence of local modifications:Exegesis of Junio Hamano's response
What happens still wasn't clear to me, so I conducted a few experiments to fix ideas, and here is my interpretation of Junio Hamano's reply. First, let me introduce some terms: let
My understanding is that, when invoked,
git checkout
, for each tracked file, gets the following diffs,git diff --staged
),git diff
),and checks whether those changes apply cleanly on the target commit, Ct. If there is any conflict, the checkout operation is aborted. Otherwise,
HEAD
moves to Ct, and the states of the staging area (Is) and working tree (Ws) are preserved.Application to your example
Before
git checkout master
Right after the line
in your example, your repo looks like this:
The pages next to the commit, staging area, and working tree, symbolizes the contents of your
README
file in the correspondings three "Git areas"."During"
git checkout master
Then you run
So, under the assumption that my interpretation of Junio's anwer is correct, what happens here?
Because both the current branch,
new-branch
, and the branch to checked out,master
, point to the same commitA
, both Cs and Ct (using my terminology) correspond toA
. In that case, of course, diff(Cs,Is) and diff(Cs,Ws) apply cleanly to Ct; no conflict here.Therefore, the checkout operation is carried out:
HEAD
is made to point tomaster
,After
git checkout master
Because no conflit arose when checking out
master
, local modifications toREADME
in the working tree have been kept, so that they can be committed to themaster
branch.