Git has this general issue of cramming about eight or ten different things into one command. Note: Git 2.23 is splitting some of these up—helpful, to be sure, but also a very big change. (Should Git 2.23 be called Git 3.0? Git 2.0 changed the behavior of git add
, which seems to me similar in degree.) See also VonC's answer.
git checkout
can update the working tree, and usually does.
It can change where HEAD
points, and sometimes does, sometimes doesn't.
It can overwrite work you did to a file, in case you want to reset the file and undo your work. Or it can refuse to overwrite work you did to a file, leaving it unchanged while either changing HEAD
, or not changing HEAD
.
The thing about all this is that, while it's remarkably difficult to describe, it actually all makes sense and after a while you get used to this and find that the one command does what you mean, most of the time. (It's that "most of the time" that can be a problem, of course....)
Anyway, the particular behavior you're seeing is a deliberate feature. Let's say you start out on branch main
, as most repositories do:
$ git clone ...
$ git branch
* main
$
At this point you might edit some file(s), get some work going, and only then realize: "gah! I meant to do this on branch develop
!"1
What Git lets you do at this point is switch to (or create) branch develop
, keeping your modifications, under one condition: that switching to develop
does not require wiping them out. Let's say you modified file f1
and created a new f2
, and you now want to create and check out local branch develop
that should start from, and automatically "track",2 origin/develop
:
$ git checkout develop
(in very old versions of git, you have to spell this git checkout -b develop --track origin/develop
).
Let's also say that file f1
is the same at the tips of branch main
and branch develop
.3 What this means, to git, is that it can do this checkout, because it does not have to modify file f1
, so it can leave your existing changes to f1
in place.
If file f2
is also the same in both commits, or (as in this case) does not exist in either one, then no files will be clobbered, and git checkout
will create your new local branch develop
, modifying the work tree to match origin/develop
as needed—and this does not include modifying f1
, nor removing f2
, so the work you have done so far remains intact.
This allows you to commit your new changes to your local develop
.
(If you run into cases where Git does have to undo your changes, but still want to "move" them to another branch, the usual trick is to use the git stash
script. This sounds like a simple thing, and git stash
is often simple to use, but it is actually quite a complicated little beast under the covers. Don't worry about that until you need it, though.)
1This happens to me all the time. Many times I want to create a new non-tracking branch, which is a bit simpler than switching to an existing branch, but the principle still applies.
2This automatic tracking allows you to more easily bring in changes other people have done: once Git picks them up with git fetch
, Git will inform you about those other-people's-changes, and let you use git merge
or git rebase
to combine your changes with theirs, without a lot of extra poking-about to figure out whose changes go where.
3Since you're new to Git, concepts like distinguishing "the tip of a branch", which is a specific commit, from "the branch", which is actually ambiguous—there are branch labels, and then there are branch structures formed by the commit tree—is something else you mostly should ignore for a while. The main thing to note is that there's a special file in the Git repository named HEAD
, and in that special file, git writes the string ref: refs/heads/main
or ref: refs/heads/develop
, in order to keep track of which branch you're on. So git checkout X
will write ref: refs/heads/X
into HEAD
once it switches to branch X
.
Meanwhile, another set of special files in the repository tell Git that branch main
refers to one of those big ugly SHA-1s like c06f8d11b75e28328cdc809397eddd768ebeb533
. This is the "tip" of branch main
. When you make a new commit on main
, Git creates the new commit "one past the old tip", then writes the new SHA-1 into the branch files, so that main
is now your new commit.
The precise details don't matter so much as the idea that new commits simply advance the branch-tip.
git status
show ? – Altorelievo