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.