I want switch branches and have git always set the working directory contents to reflect the state of the branch I'm switching to. I'm experiencing the behaviors stated here https://web.archive.org/web/20160331103129/http://www.gitguys.com/topics/switching-branches-without-committing - git is making a decision** on whether to carry certain files from the working directory associated with my current branch to the branch I switch to.
** This seems to conflict with what's stated here https://www.atlassian.com/git/tutorials/using-branches: "Checking out a branch updates the files in the working directory to match the version stored in that branch..."
In some cases I don't want git to make a decision - I just want Git to make the working directory match the state of the new branch. However, I don't want to lose any changes. I'm willing to commit or stash prior.
As jthill said in comments, you will need to figure out what to do with untracked and/or ignored files.
Moreover, you must not be in the middle of a conflicted merge.
There is a simple solution to all of this (use a different work-tree, perhaps from a different clone or perhaps from
git worktree if your Git is new enough). If that's not desirable for some reason, though, here are some things to consider. Let's take a somewhat hypothetical example, but show some real-world problems. Suppose you're in repo
project, currently on branch
$ cd project $ git status On branch dev-a You have unmerged paths. (fix conflicts and run "git commit") ...
In this case, you are really quite stuck. Anything you do here will lose your partially-merged state. If there is nothing important in the merged state, you can run
git merge --abort to stop merging, and throw out the conflicted index, and now we're back to the previous case. Let's see if Git thinks everything is clean.
$ git merge --abort $ git status On branch dev-a nothing to commit, working tree clean
Apparently everything is clean. But wait!
$ cat foo I am a foo $ git checkout dev-b Switched to branch 'dev-b' $ git status On branch dev-b Untracked files: (use "git add <file>..." to include in what will be committed) foo nothing added to commit but untracked files present (use "git add" to track)
Was everything clean? Well, file
foo is ignored in branch
dev-a, but not in branch
dev-b, where it now shows up as "untracked". We can use
git status --ignored from
dev-a to see it:
$ git checkout dev-a Switched to branch 'dev-a' $ git status --short --ignored !! foo
Untracked and/or ignored files will now show up as
!! (in the short output—the long output is as already seen).
If you wish to simply remove ignored files, you can use
git clean with the
-x option (in addition to any usual options).
If you want to save them first, you may use
git stash -a. The stash code will make three commits, instead of the usual two; the third commit will hold the untracked and ignored files. (Note that
git stash -u saves only the untracked, not the ignored, files, in the third commit.) After saving the files, the stash code will remove them, leaving you with a clean (as in
git clean -fdx) work-tree.
Note that a file that is ignored (and therefore not untracked) in one branch, such as
dev-a, can be non-ignored, and therefor either untracked or tracked (but not both) in another branch, such as
dev-c. If file
foo is ignored in
dev-a and you switch to
dev-b it becomes untracked, as we saw. But what if it's ignored in
dev-a and tracked in
$ git status --short --ignored !! foo $ git checkout dev-c Switched to branch 'dev-c' $ cat foo I am a foo $ git checkout dev-a Switched to branch 'dev-a' $ cat foo cat: foo: No such file or directory
foo is tracked in
dev-c and ignored in
dev-a, so it gets removed when we switch from
dev-c (because that's what changing from the tip commit of
$ git diff --name-status dev-c dev-a A .gitignore D foo
There's another very tricky case here. Remember that
foo is ignored in (the tip commit of)
dev-a, untracked in
dev-b, and consists of
I am a foo\n in
dev-c. As we just saw, switching from
dev-c extracted the version from
dev-c. This is true regardless of what we put in it:
$ git rev-parse --abbrev-rev HEAD dev-a $ echo 'If the foo s.its, wear it' > foo $ git checkout dev-c Switched to branch 'dev-c' $ cat foo I am a foo
Let's get back to
dev-a and put our twisted Foo Bird joke back again, and this time, let's step through branch (and tip commit)
dev-b, where file
foo is untracked, rather than ignored:
$ git checkout dev-a Switched to branch 'dev-a' $ echo 'If the foo s.its, wear it' > foo $ git checkout dev-b $ git status --short ?? foo $ git checkout dev-c error: The following untracked working tree files would be overwritten by checkout: foo Please move or remove them before you switch branches. Aborting
This is because an ignored file is also a clobberable (clobber-worthy?) file, but an untracked file is not. There is, in Git's little mind, no notion of a path-name that should not be complained-about during
git status (i.e., ignored for
git status, and skipped over when adding "all" files in some directory), yet is also precious (must never be clobbered by
All of these are complicated even further by files marked, in the index, with the
--assume-unchanged flags. I have not tested out these additional corner cases—but all are avoided by using a separate work-tree, rather than trying to cram everything into a single work-tree. (The reason is that a separate work-tree also implies a separate index. The
git worktree code does this fairly well, or you can simply clone the repository locally, which is smart enough to share files when possible.)