Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `git checkout <branch> <file>` stage the change?

If I start from a clean working tree and run git checkout <branch> <file>, where <branch> has a different version of this file, I end up with a staged rather than an unstaged change.

What's the reason for this? Is this just for consistency with other commands like git mv, which you would expect to stage changes? Is it for convenience when using git checkout to resolve merge conflicts? Or is there some other rationale?

It seems mildly odd to me since just using git checkout <branch> <file> does not offer any indication of whether I plan to commit the change.

like image 937
Luke Avatar asked May 24 '17 15:05

Luke


People also ask

Does git checkout affect staged files?

@Thomson - Yes; in fact, that is the most common use of git checkout . When that happens, you switch HEAD to point to another branch or commit, and the entire staging area is changed to match that branch/commit.

What happens when you git checkout a branch?

The git checkout command lets you navigate between the branches created by git branch . Checking out a branch updates the files in the working directory to match the version stored in that branch, and it tells Git to record all new commits on that branch.

Does git checkout overwrite local changes?

The reason for error messages like these is rather simple: you have local changes that would be overwritten by the incoming new changes that a "git pull" would bring in. For obvious safety reasons, Git will never simply overwrite your changes.

Does git checkout update local files?

DESCRIPTION. Updates files in the working tree to match the version in the index or the specified tree. If no paths are given, git checkout will also update HEAD to set the specified branch as the current branch.


2 Answers

It's really an implementation detail that the Git authors chose to let show through.

Git cannot—or rather, at one point, could not—read files directly from the repository into the work-tree. It has (or had) to pass them through an intermediary first: it had to copy them, or at least their vital statistics,1 somewhere else. Only then could Git copy the data to a work-tree file.2 The "somewhere else" is an index entry. The index is also called the staging area.

When you git checkout an entire commit, this is what you want anyway. So the internal limitation, of copying to the index first and only then to the work-tree, was actually a plus. So this mechanism, of copying into the index first, and only then on into the work-tree, was embedded into the implementation. Then, eventually, the user-oriented git checkout front end gained the ability to check out one individual file, or some small subset of files ... and it continued to do so through the index. The implementation detail became a documented feature.

Note that sometimes, the index is in use as a helper area during a conflicted merge. In this case, for some file F, there are up to three entries, in numbered slots 1 (base), 2 (--ours), and 3 (--theirs), instead of just one entry in the normal slot-zero. When this is so, you can extract any of the three index slot entries to the work-tree without disturbing the index. But if you use git checkout to extract a file from some other commit-or-tree, Git copies the file into the index, writing it to slot zero. This has the side effect of removing the higher-numbered slots, resolving the merge conflict!


1The main one is the hash ID. As ElpieKay noted in a comment, Git has to resolve the commit hash to a tree hash, then search the various trees to find whichever file(s) is/are of interest, so that it can obtain the blob hash. The index entry itself has a bunch more data as well, though, including stat structure data for the work-tree file, to make Git go fast.

2You can still use this work-flow, by using git read-tree to copy a tree into the index, then using git checkout-index to copy the index to the work-tree. Originally, Git consisted of a bunch of shell scripts like git-checkout wrapped around some fundamental C-coded pieces like git-read-tree. (The names were all hyphenated like this and there was no front end git command.)

like image 169
torek Avatar answered Oct 18 '22 04:10

torek


The Short Answer

  1. git checkout always copies items out of the index into the worktree.

  2. If you specify a commit other than the one you're on (e.g. the HEAD of another branch), checkout will always first copy the items from that commit into the index.

  3. Anything in the index that differs from HEAD will show as a "staged change". This is by definition.

See also Git checkout file from branch without changing index

like image 25
Inigo Avatar answered Oct 18 '22 02:10

Inigo