Confused by git checkout
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",2origin/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.
When you create a branch, that branch will automatically get the files of the branch you were in when you created this new branch.
Let's say you are in main
branch and you want to create a develop
branch. All together should look like this:
git checkout -b develop # create and switch to develop branch
touch text.txt # create a file
git add . # add file to staging area
git commit -m "adding text.txt"
git checkout main
And then you won't see text.txt
since you are in main
.
That confusion is acknowledged by Git 2.23.
Git 2.23 (Q3 2019) will replace git checkout
with two new commands:
git switch
git restore
(illustrated here)
See commit 97ed685, commit d16dc42, commit bcba406 (20 Jun 2019), commit 4e43b7f, commit 1235875, commit 80f537f, commit fc991b4, commit 75f4c7c, commit 4df3ec6, commit 2f0896e, commit a5e5f39, commit 3a733ce, commit e3ddd3b, commit 183fb44, commit 4058199, commit a6cfb9b, commit be8ed50, commit c9c935f, commit 46e91b6 (25 Apr 2019), and commit 328c6cb (29 Mar 2019) by Nguyễn Thái Ngọc Duy (pclouds
).
(Merged by Junio C Hamano -- gitster
-- in commit f496b06, 09 Jul 2019)
checkout
: split part of it to new command 'switch
'
"
git checkout
" doing too many things is a source of confusion for many users (and it even bites old timers sometimes).
To remedy that, the command will be split into two new ones: switch and restore. The good old "git checkout
" command is still here and will be until all (or most of users) are sick of it.
And:
switch: reject if some operation is in progress
Unless you know what you're doing, switching to another branch to do something then switching back could be confusing. Worse, you may even forget that you're in the middle of something. By the time you realize, you may have done a ton of work and it gets harder to go back.
A new option
--ignore-in-progress
was considered but dropped because it was not exactly clear what should happen.
Sometimes you can switch away and get back safely and resume the operation. Sometimes not.
And thegit-checkout
behavior is automatically clear merge/revert/cherry-pick, which makes it a bit even more confusing.
See this discussion.We may revisit and add this option in the future.
But for now play it safe and not allow it (you can't even skip this check with--force
).
The user is suggested to cancel the operation by themselves (and hopefully they do consider the consequences, not blindly type the command), or to create a separate worktree instead of switching.The third option is the good old "
git checkout
", but it's not mentioned.
See git switch
man page
DESCRIPTION
Switch to a specified branch.
The working tree and the index are updated to match the branch.
All new commits will be added to the tip of this branch.Optionally a new branch could be created with either
-c
,-C
, automatically from a remote branch of same name (see--guess
), or detach the working tree from any branch with--detach
, along with switching.Switching branches does not require a clean index and working tree (i.e. no differences compared to
HEAD
).
The operation is aborted however if the operation leads to loss of local changes, unless told otherwise with--discard-changes
or--merge
.
EXAMPLES
The following command switches to the "
main
" branch:$ git switch main
After working in the wrong branch, switching to the correct branch would be done using:
$ git switch mytopic
However, your "wrong" branch and correct "
mytopic
" branch may differ in files that you have modified locally, in which case the above switch would fail like this:$ git switch mytopic error: You have local changes to 'frotz'; not switching branches.
You can give the
-m
flag to the command, which would try a three-way merge:$ git switch -m mytopic Auto-merging frotz
After this three-way merge, the local modifications are not registered in your index file, so
git diff
would show you what changes you made since the tip of the new branch.To switch back to the previous branch before we switched to
mytopic
(i.e. "main
" branch):$ git switch -
You can grow a new branch from any commit.
For example, switch to "HEAD~3
" and create branch "fixup
":$ git switch -c fixup HEAD~3 Switched to a new branch 'fixup'
If you want to start a new branch from a remote branch of the same name:
$ git switch new-topic Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin' Switched to a new branch 'new-topic'
To check out commit
HEAD~3
for temporary inspection or experiment without creating a new branch:$ git switch --detach HEAD~3 HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
If it turns out whatever you have done is worth keeping, you can always create a new name for it (without switching away):
$ git switch -c good-surprises
Note the error messages that "git switch
" mentions its option to create a new branch, "-b/-B
" options were shown, where "-c/-C
" options should be, which has been corrected with Git 2.27 (Q2 2020).
See commit 7c16ef7 (30 Apr 2020) by Denton Liu (Denton-L
).
(Merged by Junio C Hamano -- gitster
-- in commit f4675f3, 08 May 2020)
switch
: fix errors and comments related to -c and -CReported-by: Robert Simpson
Signed-off-by: Denton Liu
Reviewed-by: Taylor Blau
In d787d311db ("
checkout
: split part of it to new command 'switch'", 2019-03-29, Git v2.23.0-rc0 -- merge listed in batch #4), thegit switch
command was created by extracting the common functionality ofcmd_checkout()
incheckout_main()
.However, in b7b5fce270 ("
switch
: better names for-b
and-B
", 2019-03-29, Git v2.23.0-rc0 -- merge listed in batch #4), the branch creation and force creation options for 'switch
' were changed to-c
and-C
, respectively.As a result of this, error messages and comments that previously referred to
-b
and-B
became invalid forgit switch
.For error messages that refer to
-b
and-B
, use a format string instead so that-c
and-C
can be printed whengit switch
is invoked.