Git says 'commit your changes or stash them before you switch branches', but also 'nothing to commit'

TL;DR

You need to clear the skip-worktree bit and re-run git status to see what's going on. From that point forward, things will be (somewhat) clearer.

Long(ish)

The problem here was that the skip-worktree bit was set on:

path/to/file.xml

There's a related bit spelled assume-unchanged. Both bits have the same actual effect. Neither is meant for the way people tend to use them, though documentation and stackoverflow answers recommend the skip-worktree bit,1 and so do I here; but either one does the same thing in practice. You do have to remember (or re-discover) which bit you set, in order to clear it:

git update-index --no-skip-worktree path/to/file.xml

When either bit is set on a file whose name and contents are recorded in the index, Git assumes that the contents stored via the index should be used, and the work-tree copy should be ignored during git status and git add operations.

Fortunately, Git is smart enough to check the actual work-tree copy on other operations. If Git is about to overwrite the work-tree copy for some reason—such as git checkout or git merge of a commit whose committed copy of that file differs from the current index copy—Git will double-check that the work-tree copy of path/to/file.xml matches the index copy. If not, Git will complain that the operation will overwrite the work-tree copy.

Unfortunately, git status, by design, doesn't announce that the work-tree copy is out of sync with the index copy. It just assumes that both versions of the file match. So you run git status and there's no changes to commit and hence nothing to save, but meanwhile git checkout or git merge keeps complaining that you must commit your changes.

Clearing the bit, whichever bit it is, makes git status notice the problem. It seems to me that git status should be more informative here: it needs to say, perhaps when using an extra option or perhaps just always, that there is some difference here but it's being deliberately ignored due to one or both of these bits. (To make this work well with sparse checkout, it probably should say nothing about a marked---skip-worktree-file that's in the index, not in the work-tree, and excluded by the sparseness rules.)


1Assume-unchanged is meant for use on file systems where the lstat call is particularly slow, and Git is allowed to ignore it. Skip-worktree is meant for use with sparse checkout, and Git is not allowed to ignore this. Git has no user-oriented sparse checkout commands either, so setting the skip-worktree bit is better.

Tags:

Git