511

Most of the time when I try to checkout another existing branch, Git doesn't allow me if I have some uncommitted changes on the current branch. So I'll have to commit or stash those changes first.

However, occasionally Git does allow me to checkout another branch without committing or stashing those changes, and it will carry those changes to the branch I checkout.

What is the rule here? Does it matter whether the changes are staged or unstaged? Carrying the changes to another branch doesn't make any sense to me, why does git allow it sometimes? That is, is it helpful in some situations?

0

10 Answers 10

523

Preliminary notes

This answer is an attempt to explain why Git behaves the way it does. It is not a recommendation to engage in any particular workflows. (My own preference is to just commit anyway, avoiding git stash and not trying to be too tricky, but others like other methods.)

The observation here is that, after you start working in branch1 (forgetting or not realizing that it would be good to switch to a different branch branch2 first), you run:

git checkout branch2

Sometimes Git says "OK, you're on branch2 now!" Sometimes, Git says "I can't do that, I'd lose some of your changes."

If Git won't let you do it, you have to commit your changes, to save them somewhere permanent. You may want to use git stash to save them; this is one of the things it's designed for. Note that git stash save or git stash push actually means "Commit all the changes, but on no branch at all, then remove them from where I am now." That makes it possible to switch: you now have no in-progress changes. You can then git stash apply them after switching.

Sidebar: git stash save is the old syntax; git stash push was introduced in Git version 2.13, to fix up some problems with the arguments to git stash and allow for new options. Both do the same thing, when used in the basic ways.

You can stop reading here, if you like!

If Git won't let you switch, you already have a remedy: use git stash or git commit; or, if your changes are trivial to re-create, use git checkout -f to force it. This answer is all about when Git will let you git checkout branch2 even though you started making some changes. Why does it work sometimes, and not other times?

The rule here is simple in one way, and complicated/hard-to-explain in another:

You may switch branches with uncommitted changes in the work-tree if and only if said switching does not require clobbering those changes.

That is—and please note that this is still simplified; there are some extra-difficult corner cases with staged git adds, git rms and such—suppose you are on branch1. A git checkout branch2 would have to do this:

  • For every file that is in branch1 and not in branch2,1 remove that file.
  • For every file that is in branch2 and not in branch1, create that file (with appropriate contents).
  • For every file that is in both branches, if the version in branch2 is different, update the working tree version.

Each of these steps could clobber something in your work-tree:

  • Removing a file is "safe" if the version in the work-tree is the same as the committed version in branch1; it's "unsafe" if you've made changes.
  • Creating a file the way it appears in branch2 is "safe" if it does not exist now.2 It's "unsafe" if it does exist now but has the "wrong" contents.
  • And of course, replacing the work-tree version of a file with a different version is "safe" if the work-tree version is already committed to branch1.

Creating a new branch (git checkout -b newbranch) is always considered "safe": no files will be added, removed, or altered in the work-tree as part of this process, and the index/staging-area is also untouched. (Caveat: it's safe when creating a new branch without changing the new branch's starting-point; but if you add another argument, e.g., git checkout -b newbranch different-start-point, this might have to change things, to move to different-start-point. Git will then apply the checkout safety rules as usual.)


1This requires that we define what it means for a file to be in a branch, which in turn requires defining the word branch properly. (See also What exactly do we mean by "branch"?) Here, what I really mean is the commit to which the branch-name resolves: a file whose path is P is in branch1 if git rev-parse branch1:P produces a hash. That file is not in branch1 if you get an error message instead. The existence of path P in your index or work-tree is not relevant when answering this particular question. Thus, the secret here is to examine the result of git rev-parse on each branch-name:path. This either fails because the file is "in" at most one branch, or gives us two hash IDs. If the two hash IDs are the same, the file is the same in both branches. No changing is required. If the hash IDs differ, the file is different in the two branches, and must be changed to switch branches.

The key notion here is that files in commits are frozen forever. Files you will edit are obviously not frozen. We are, at least initially, looking only at the mismatches between two frozen commits. Unfortunately, we—or Git—also have to deal with files that aren't in the commit you're going to switch away from and are in the commit you're going to switch to. This leads to the remaining complications, since files can also exist in the index and/or in the work-tree, without having to exist these two particular frozen commits we're working with.

2It might be considered "sort-of-safe" if it already exists with the "right contents", so that Git does not have to create it after all. I recall at least some versions of Git allowing this, but testing just now shows it to be considered "unsafe" in Git 1.8.5.4. The same argument would apply to a modified file that happens to be modified to match the to-be-switch-to branch. Again, 1.8.5.4 just says "would be overwritten", though. See the end of the technical notes as well: my memory may be faulty as I don't think the read-tree rules have changed since I first started using Git at version 1.5.something.


Does it matter whether the changes are staged or unstaged?

Yes, in some ways. In particular, you can stage a change, then "de-modify" the work tree file. Here's a file in two branches, that's different in branch1 and branch2:

$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth

At this point, the working tree file inboth matches the one in branch2, even though we're on branch1. This change is not staged for commit, which is what git status --short shows here:

$ git status --short
 M inboth

The space-then-M means "modified but not staged" (or more precisely, working-tree copy differs from staged/index copy).

$ git checkout branch2
error: Your local changes ...

OK, now let's stage the working-tree copy, which we already know also matches the copy in branch2.

$ git add inboth
$ git status --short
M  inboth
$ git checkout branch2
Switched to branch 'branch2'

Here the staged-and-working copies both matched what was in branch2, so the checkout was allowed.

Let's try another step:

$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches

The change I made is lost from the staging area now (because checkout writes through the staging area). This is a bit of a corner case. The change is not gone, but the fact that I had staged it, is gone.

Let's stage a third variant of the file, different from either branch-copy, then set the working copy to match the current branch version:

$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth

The two Ms here mean: staged file differs from HEAD file, and, working-tree file differs from staged file. The working-tree version does match the branch1 (aka HEAD) version:

$ git diff HEAD
$

But git checkout won't allow the checkout:

$ git checkout branch2
error: Your local changes ...

Let's set the branch2 version as the working version:

$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
 this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...

Even though the current working copy matches the one in branch2, the staged file does not, so a git checkout would lose that copy, and the git checkout is rejected.

Technical notes—only for the insanely curious :-)

The underlying implementation mechanism for all of this is Git's index. The index, also called the "staging area", is where you build the next commit: it starts out matching the current commit, i.e., whatever you have checked-out now, and then each time you git add a file, you replace the index version with whatever you have in your work-tree.

Remember, the work-tree is where you work on your files. Here, they have their normal form, rather than some special only-useful-to-Git form like they do in commits and in the index. So you extract a file from a commit, through the index, and then on into the work-tree. After changing it, you git add it to the index. So there are in fact three places for each file: the current commit, the index, and the work-tree.

When you run git checkout branch2, what Git does underneath the covers is to compare the tip commit of branch2 to whatever is in both the current commit and the index now. Any file that matches what's there now, Git can leave alone. It's all untouched. Any file that's the same in both commits, Git can also leave alone—and these are the ones that let you switch branches.

Much of Git, including commit-switching, is relatively fast because of this index. What's actually in the index is not each file itself, but rather each file's hash. The copy of the file itself is stored as what Git calls a blob object, in the repository. This is similar to how the files are stored in commits as well: commits don't actually contain the files, they just lead Git to the hash ID of each file. So Git can compare hash IDs—currently 160-bit-long strings—to decide if commits X and Y have the same file or not. It can then compare those hash IDs to the hash ID in the index, too.

This is what leads to all the oddball corner cases above. We have commits X and Y that both have file path/to/name.txt, and we have an index entry for path/to/name.txt. Maybe all three hashes match. Maybe two of them match and one doesn't. Maybe all three are different. And, we might also have another/file.txt that's only in X or only in Y and is or is not in the index now. Each of these various cases requires its own separate consideration: does Git need to copy the file out from commit to index, or remove it from index, to switch from X to Y? If so, it also has to copy the file to the work-tree, or remove it from the work-tree. And if that's the case, the index and work-tree versions had better match at least one of the committed versions; otherwise Git will be clobbering some data.

(The complete rules for all of this are described in, not the git checkout documentation as you might expect, but rather the git read-tree documentation, under the section titled "Two Tree Merge".)

15
  • 7
    ... there's also git checkout -m, which merges your worktree and index changes into the new checkout.
    – jthill
    Commented Feb 19, 2017 at 14:53
  • 1
    Thanks for this excellent explanation! But where can I find the information in the official docs? Or are they incomplete? If so, what's the authoritative reference for git (hopefully other than its source code)?
    – max
    Commented Feb 28, 2017 at 9:51
  • 2
    (1) you can't, and (2) the source code. The main problem is that Git is constantly evolving. For instance, right now, there is a big push to augment or ditch SHA-1 with or in favor of SHA-256. This particular part of Git has been pretty stable for a long time now, though, and the underlying mechanism is straightforward: Git compares the current index to the current and target commits and decides which files to change (if any) based on the target commit, then tests the "cleanness" of work-tree files if the index entry would need replacing.
    – torek
    Commented Feb 28, 2017 at 9:58
  • 17
    Short answer: There is a rule, but it's too obtuse for the average user to have any hope of understanding let alone remembering, therefore instead of relying on the tool to behave intelligibly you should instead rely on the disciplined convention of only checking out when your current branch is committed and clean. I don't see how this answers the question of when it would ever be useful to carry outstanding changes over to another branch, but I may have missed it because I struggle to understand it.
    – Neutrino
    Commented Apr 7, 2017 at 10:18
  • 2
    @HawkeyeParker: this answer has undergone numerous edits, and I'm not sure any of them improved it much, but I'll try adding something about what it means for a file to be "in a branch". Ultimately this is going to be wobbly because the notion of "branch" here is not properly defined in the first place, but that's yet another item.
    – torek
    Commented May 17, 2018 at 20:59
79

You have two choices: stash your changes:

git stash

then later to get them back:

git stash apply

or put your changes on a branch so you can get the remote branch and then merge your changes onto it. That's one of the greatest things about git: you can make a branch, commit to it, then fetch other changes on to the branch you were on.

You say it doesn't make any sense, but you are only doing it so you can merge them at will after doing the pull. Obviously your other choice is to commit on your copy of the branch and then do the pull. The presumption is you either don't want to do that (in which case I am puzzled that you don't want a branch) or you are afraid of conflicts.

5
  • 1
    Isn't the correct command git stash apply? here the docs.
    – Thomas8
    Commented Dec 3, 2015 at 21:14
  • 1
    Just what I was looking for, to temporarily switch to different branches, to look up something and get back to the same state of the branch I am working on. Thanks Rob!
    – Naishta
    Commented Nov 3, 2016 at 21:22
  • 1
    Yeah, this is the right way to do this. I appreciate the detail in the accepted answer, but that's making things harder than they need to be. Commented Jan 18, 2017 at 22:54
  • 7
    Also, if you don't have any need to keep the stash around, you can use git stash pop and it will drop the stash from your list if it successfully applies. Commented Jan 18, 2017 at 22:55
  • 5
    better use git stash pop, unless you intend to keep a log of stashes in your repo history Commented Jan 11, 2019 at 12:59
24

If the new branch contains edits that are different from the current branch for that particular changed file, then it will not allow you to switch branches until the change is committed or stashed. If the changed file is the same on both branches (that is, the committed version of that file), then you can switch freely.

Example:

$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"

$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
     # experiment now contains changes that master doesn't have
     # any future changes to this file will keep you from changing branches
     # until the changes are stashed or committed

$ echo "and we're back" >> file.txt  # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

This goes for untracked files as well as tracked files. Here's an example for an untracked file.

Example:

$ git checkout -b experimental  # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"

$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
    file.txt
Please move or remove them before you can switch branches.
Aborting

A good example of why you WOULD want to move between branches while making changes would be if you were performing some experiments on master, wanted to commit them, but not to master just yet...

$ echo 'experimental change' >> file.txt # change to existing tracked file
   # I want to save these, but not on master

$ git checkout -b experiment
M       file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"
2
  • Actually I still don't quite get it. In your first example, after you added "and we're back", it says the local change will be overwritten, what local change exactly? "and we're back"? Why doesn't git just carry this change to master so that in master the file contains "hello world" and "and we're back"
    – Xufeng
    Commented Feb 26, 2014 at 22:46
  • 1
    In the first example master only has 'hello world' committed. experiment has 'hello world\ngoodbye world' committed. For the branch change to take place, file.txt needs to be modified, the problem is, there are uncommitted changes "hello world\ngoodbye world\nand we're back".
    – Gordolio
    Commented Feb 26, 2014 at 22:55
8

The correct answer is

git checkout -m origin/master

It merges changes from the origin master branch with your local even uncommitted changes.

4
  1. The branch switch only happens when you change a file that has no diff between the two branches. In that case git treats that change common for both the files.
  2. This gets prevented when you change a file whose diff exists between the two branches. In that case you get ABORT signal.

Came to this conclusion after and hour of investigation with local testing.

2
  • Thanks. This certainly seems to also answer my question here
    – Tryer
    Commented Apr 9, 2022 at 0:33
  • This is more subtle than that, branch switch can happen even when you change a file whose diff exists between the two branches, if the changes removes the diff and is staged, cf. the section ‘Does it matter whether the changes are staged or unstaged?’ of this answer.
    – Géry Ogam
    Commented Nov 14, 2022 at 23:01
1

I have faced the same question recently. What I understand is, if the branch you are checking in has a file which you modified and it happens to be also modified and committed by that branch. Then git will stop you from switching to the branch to keep your change safe before you commit or stash.

1

I've been struggling for a while with this thing too and I would like to give my two cents to the answer. First thing first, my understanding of the matter came from here: https://medium.com/swimm/a-visualized-intro-to-git-internals-objects-and-branches-68df85864037

The question was:

However, occasionally Git does allow me to checkout another branch without committing or stashing those changes, and it will carry those changes to the branch I checkout.

What is the rule here? Does it matter whether the changes are staged or unstaged? Carrying the changes to another branch doesn't make any sense to me, why does git allow it sometimes? That is, is it helpful in some situations?

When you create a branch out of any other branch, you are only creating a pointer to the same commit, so unless you have committed any change you had begun working on, you will be pointing at the same commit, and thus git will let you change branches that way. Only when you commit any change to the new branch is that commits begin to differ between branches and git will complain when trying to checkout those branches if there were any uncommitted changes.

0

In case you don't want this changes to be committed at all do git reset --hard.

Next you can checkout to wanted branch, but remember that uncommitted changes will be lost.

0

If you want to checkout a branch from an existing one after making uncommitted changes in it so that those changes are also uncommitted in the new branch, type git checkout -b <new-branch>. This will create a new branch and you can commit your changes into that new branch.

0

You can achieve it by:

  1. Unstage all files
  2. Checkout another branch
  3. Stage (not stash) changes to the current branch.

Not the answer you're looking for? Browse other questions tagged or ask your own question.