Edit per updates: Something is definitely odd here, though it's hard to say for sure what (the captured output is images so it's not possible to check for weird Unicode issues, for instance). No file should ever be listed twice in git ls-tree -r
output on a commit-tree. I have never seen this kind of behavior from Git. There are issues with uppercase vs lowercase, especially on Windows and MacOS, that can cause this kind of behavior, but that doesn't match what you're showing here.
Original answer below
First, a quick note: your tags mention MacOS. MacOS file systems are by default case-insensitive, so that if you have a file named README.TXT
and ask the system to view the file named readme.txt
, it shows you README.TXT
. If you ask the system to add a new file named readme.txt
it will instead overwite the existing README.TXT
and keep the uppercase name. So watch out for files whose name differs only in case: the Git commit might have a myproj.sln
file that will overwrite your MyProj.sln
file.
There are at least two possibilities, but let's take the most likely first:
This means there is a file that is not in the current index and commit, but is in the current work-tree, named MyProj.sln
. This same file is in the commit you've asked Git to git checkout
. So Git will overwrite the work-tree file if you do successfully git checkout
that other commit. Git is warning you that you will lose the current contents of that file.
Or, there is a file that is in the current index and work-tree, named MyProj.sln
. The work-tree copy does not match the index copy. Normally, git status
would tell you that the file is modified, but you've set one of the two index flag bits that tell Git: don't look for changes to the file, and/or don't tell me about changes if you accidentally find some, just keep the index copy around the way it is. These two flag bits are --assume-unchanged
and --skip-worktree
.
In both cases, if you do successfully check out the commit you're asking Git to check out, that will overwrite the work-tree copy of MyProj.sln
. If that is OK, just go ahead and remove the file now, and the git checkout
will proceed.
To see which is the case:
git ls-files --stage --debug MyProj.sln
If you get no output, the file is not in the index (and hence also not in the current commit, based on the git status
output, or lack of output). That in turn means that it's just an untracked work-tree file at the moment.
If you do get output, it should resemble this output that I get here for a different file:
$ git ls-files --stage --debug Makefile
100644 b08d5ea258c69a78745dfa73fe698c11d021858a 0 Makefile
ctime: <number>:<number>
mtime: <number>:<number>
dev: <number> ino: <number>
uid: <number> gid: <number>
size: <number> flags: <number>
The flags: <number>
shows the assume-unchanged and skip-worktree bits, although not in a human-friendly format: skip-worktree is 4000
and the other is 8000
(if both are set you'll get c000
). Setting the skip-worktree bit, I actually get:
size: 96311 flags: 40004000
while setting the assume-unchanged bit gives me:
size: 96311 flags: 8000
The commit you're asking to switch to (the tip of develop
) has a committed version of the file, which you can see (without having it overwrite the current copy) with:
git show develop:MyProj.sln
Note that once you git checkout develop
, that file will be in all three active places: the current commit, the index, and the work-tree. If the tip commit of my-feature
does not have the file, switching back from develop
to my-feature
will remove the file from the work-tree. The way to remember most of this is:
- Commits are made from the index.
- Commits are therefore extracted into the index first (and then on into the work-tree).
- The index keeps track of what's in the work-tree that should be committed. The index copy is the one that goes into the commit.
git status
compares the index to the work-tree to tell you what you should copy into the index.
- When switching commits, work-tree files that aren't in the index, but need to be based on the new commit, get created; files that are in the index, but need to not be there based on the new commit, get removed.
- Files in the work-tree that aren't in the index are untracked, and are left alone.
After that point, the assume-unchanged and skip-worktree bits, if you set them, are just minor tweaks to the otherwise largely self-consistent rules. The .gitignore
rules start to make sense as well: a file listed in .gitignore
is one that git status
won't complain about as being untracked. (However, an important side effect, that .gitignore
makes Git feel free to clobber such untracked files in some cases, is tougher to remember, or explain for that matter.)