1

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.

Possible?

Howiecamp
  • 2,981
  • 6
  • 38
  • 59
  • I don't get it, if you already know that stash then commit does what you want, why not stash then commit? That "comprehensive introduction" tutorial is horrible, larded with overbearing and oversimplified and just plain misleading abstractions. – jthill Oct 29 '16 at 23:12
  • @jthill All I know is that git stash *allows* the checkout in cases where it would have not been allowed. In my testing it doesn't seem that git stash guarantees the working directory to match the checked out branch's state. Unless I tested wrong...? Is git stash followed by a checkout supposed to result in the behavior I'm looking for? – Howiecamp Oct 29 '16 at 23:26
  • 1
    If you want a complete wipe, use `-u` or `-a` on your stash, see [its docs](https://git-scm.com/docs/git-stash). By default stash saves just the currently-tracked state. – jthill Oct 29 '16 at 23:41
  • @jthill So bottom line is, if I stash all my changes (with `git stash -u` or `git stash -a`) OR commit all my changes, when I checkout another branch my working directory *should* switch to match that of the checked-out branch's state, correct? Please confirm. As I test now it seems this *is* the behavior and that my previous tests were just wrong. – Howiecamp Oct 30 '16 at 04:15
  • Yes, uncommitted changes in your worktree or index stay in your worktree or index until you change them or tell git where to commit them. Let me repeat this: the tutorial you read about branching is _awful_. If you'd stuck with the actual git docs you'd have seen "[To prepare for working on , switch to it by updating the index and the files in the working tree, and by pointing HEAD at the branch. Local modifications to the files in the working tree are kept, so that they can be committed to the .](https://git-scm.com/docs/git-checkout#_description)" – jthill Oct 30 '16 at 09:51
  • @jthill What I'm trying to understand conceptually and the philosophy behind is "Local modifications to the files in the working tree are kept, so that they can be committed to the ". Why from a design point of view does Git assume that you want to carry your working directory changes over to the branch you're switching to rather than assuming you want to leave them (for the meantime) associated with the current branch? – Howiecamp Oct 30 '16 at 17:07
  • @jthill what if I don't want to carry over committed changes when I switch branches? Understood about those docs being awful. – Howiecamp Apr 26 '17 at 14:17
  • Can you be more concrete here? This might deserve a separate question. – jthill Apr 26 '17 at 15:20
  • @jthill Please disregard my question, I don't know what I was thinking. Commits made on a branch obviously aren't reflected on other branches until they're merged. – Howiecamp Apr 30 '17 at 18:48

2 Answers2

3

Uncommitted changes remain in your worktree or index until you tell git where to commit them.

git add, git checkout and git reset all have a --patch option. The workflow is, doing what needs doing and recording it where it needs recording are separate tasks. Often enough you find yourself doing effectively drive-by bugfixing, where you find a complete mess someoneusually me left and the issue is, there's doing the work, and doing the paperwork, but you're staring right straight at the work and know exactly what needs doing.

The git way1 is, Do It. Get it all correct in your worktree (this may involve a bit of exploratory branching itself)

  • and then construct the commit series you'd have constructed in the first place if you'd known exactly what you were doing all along.

That's the important part. That's what makes professional, publishable work. What trips people up is, git's meant at least as much for the messy part that comes before that.


1hahaha he said "the git way", as if there were only onelol

jthill
  • 55,082
  • 5
  • 77
  • 137
2

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 dev-a:

$ 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 UU or !! (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-b and 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 dev-c?

$ 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

File foo is tracked in dev-c and ignored in dev-a, so it gets removed when we switch from dev-a to dev-c (because that's what changing from the tip commit of dev-c to dev-a requires):

$ 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-a to 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 git checkout).

All of these are complicated even further by files marked, in the index, with the --skip-worktree or --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.)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • +1 for a really comprehensive answer on scenarios that will impact working directory behaviors when switching branches. Great tip about git worktree too. Am I correct in my comment above? https://stackoverflow.com/questions/40324367/how-can-i-checkout-a-branch-while-ensuring-that-none-of-my-current-branchs-chan#comment67907351_40324367. – Howiecamp Oct 30 '16 at 04:17
  • 1
    @Howiecamp: mostly; there's still the question of ignored files if you use `git stash -u`, or both untracked and ignored files, plus any modified work-tree files, if you use `git commit`. The `git stash` code makes at least *two* commits, one for the index and one for the work-tree, to save those. The third commit, if made at all, saves the untracked files (`-u`) or untracked+ignored = all other files (`-a`). – torek Oct 30 '16 at 05:46
  • Understood. I wasn't looking at the whole picture of the state of working directory files and now I see it. – Howiecamp Oct 30 '16 at 17:12