Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
962 views
in Technique[技术] by (71.8m points)

macos - git checkout errors even though git status reports that working tree is clean

I am on my local my-feature branch

git status reports nothing to commit, working tree clean

I want to switch to develop branch and do git fetch and git merge there (I prefer it over git pull)

However, doing so produces error below

Here I first check status and it shows that all is clean

mymbp:MyProj username$ git status
On branch my-feature
nothing to commit, working tree clean

Next I try to checkout my develop branch which is an existing local branch

On branch my-feature
nothing to commit, working tree clean
mymbp:MyProj username$ git checkout develop
error: Your local changes to the following files would be overwritten by checkout:
    MyProj.sln
Please commit your changes or stash them before you switch branches.
Aborting

It complains that myProj.sln has been changed even though git status says nothing has changed.

Issuing git status again, confirms that nothing has changed

mymbp:MyProj username$ git status
On branch my-feature
nothing to commit, working tree clean

UPDATE 1

Doing git ls-files --stage --debug MyProj.sln shows like below and I dont see any 4000 or 8000 (--skip-worktree or --assume-unchanged flags):

mymbp:MyProj username$ git ls-files --stage --debug MyProj.sln
100644 40c3593ed572beb2139c189455274f8900a1340c 0   MyProj.sln
  ctime: 1541703970:521058155
  mtime: 1541637062:121492660
  dev: 16777220 ino: 8470003
  uid: 501  gid: 20
  size: 55684   flags: 0
mymbp:MyProj username$ 

Issuing git show develop:MyProj.sln shows me number of project files and their GUIDs in the solution, Global sections for pre and post solution but the output is very long showing just Release, Debug configurations and some GUIDS. Not sure what to do with that yet.

UPDATE 2

So, it seams as if MyProj.sln file is in work-tree but not in index and commit (HEAD). Based on @torek explanation, issuing git add MyProj.sln should add this file to index but that is not true since nothing is added and git status returns nothing before I did git add and after I did it. Meanwhile git checkout still complains that MyProj.sln has changed. git diff also returns nothing

UPDATE 3

I also found someone suggest issuing these 2 command to get hash of commit HEAD and to then see what changed in it. I see lots of files duplicate, while some do not. Those that do not appear to be files I added in my current feature branch. Those that are duplicate appear to be files from remote

mymbp:MyProj username$ git rev-parse HEAD
1ca8d8a7c5eff0f2a03eb185f1b25aff27c1d2fd
mymbp:MyProj username$ git ls-tree -r 1ca8d8a7c5eff0f2a03eb185f1b25aff27c1d2fd

And here is the output of it

enter image description here

UPDATE 4

My config is:

mymbp:MyProj username$ git config --list
credential.helper=osxkeychain
core.excludesfile=/Users/username/.gitignore_global
core.autocrlf=input
difftool.sourcetree.cmd=opendiff "$LOCAL" "$REMOTE"
difftool.sourcetree.path=
mergetool.sourcetree.cmd=/Applications/Sourcetree.app/Contents/Resources/opendiff-w.sh "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"
mergetool.sourcetree.trustexitcode=true
user.name=User Name
[email protected]
color.ui=true
color.status.changed=blue normal
color.status.untracked=red normal
color.status.added=magenta normal
color.status.updated=green normal
color.status.branch=yellow normal bold
color.status.header=white normal bold
commit.template=/Users/username/.stCommitMsg
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
remote.origin.url=https://github.com/SomeSystems/MyProj.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.develop.remote=origin
branch.develop.merge=refs/heads/develop
branch.feat-1.remote=origin
branch.feat-1.merge=refs/heads/feat/feat-1
branch.1234-refactoring.remote=origin
branch.1234-refactoring.merge=refs/heads/1234-refactoring
mymbp:MyProj username$ 
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...