问题
Problem: two files under two different name-cases in the same directory, which I didn't know at the first. So I was quite surprised to see this,
git commit -am "why"
On branch tmp
Changes not staged for commit:
modified: src/view/callCenter/seatReport/SeatSubstate.vue
Then I found origin has both SeatSubstate.vue & seatSubstate.vue in the path src/view/callCenter/seatReport
But on my mac
ls src/view/callCenter/seatReport/
... seatSubstate.vue /* did NOT show SeatSubstate.vue only seatSubstate.vue */
I know there is discussion about How do I commit case-sensitive only filename changes in Git?
But I still don't understand why git can not commit this file.
Second, how do I fix this problem? For example in that SO discussion many answered mentioned git mv
but I am not sure git mv
can fix my problem or not.
----- update -----
I suddenly realized my mac (my HD to be exactly) was not case-sensitive (APFS), refer to https://apple.stackexchange.com/questions/71357/how-to-check-if-my-hd-is-case-sensitive-or-not.
Normally it should mean SeatSubstate.vue & seatSubstate.vue are the same file, but somehow git makes them 2 different files and cause the trouble. git mv
seems to fix the problem but I am not 100% sure.
Refer to Changing capitalization of filenames in Git
回答1:
Properly defining the problem
Git is always capable of storing—in commits, and in Git's index, that is—two files under two different name-cases (e.g., both README
and readme
) in the same directory, because Git doesn't store files in operating-system directories at all. Files are either frozen in commits,1 which means they retain their form no matter whether they're on Linux or Windows or MacOS or any other system, or they are in Git's index, which is actually just a data file.2
The problem occurs because you, the human operating Git, want to use the OS-provided file system, where your computer stores files in their normal everyday form so that the rest of your computer can work with them too. This is not an unreasonable demand—Git's internal files are stored in a Git-only internal form, that only Git can use. You need to be able to use Git to get something done, not just to play with Git all day.
MacOS has the ability to provide case-sensitive file systems (that can hold both README
and readme
in the same directory) but does not do so by default. So, either by either by not using MacOS at all, or by using this ability, someone—not you—has done this sort of thing:
Then I found origin has both SeatSubstate.vue & seatSubstate.vue in the path
src/view/callCenter/seatReport
In other words, you have both files in some existing commit. As we just said, Git is perfectly capable of handling this. It's your OS that isn't.
So if you run git checkout
and select that commit, Git will copy both files to your index, which now has both spellings, SeatSubstate.vue
and seatSubstate.vue
. It also copies both files (with both spellings!) to your work-tree, but your OS can only hold one spelling, so one file erases the other and you're left with just one file with one spelling.
When Git compares the index's files and their contents to the work-tree files and their contents, Git will:
- see that, according to the index, there are two files;
- try comparing each index file to the work-tree file Git gets when it opens that name;
- complain that one of them is modified.
Here's an example, which I made by creating a repository on a Unix-y system and giving it two files, README
and readme
, with different contents, then cloning that to a Mac:
sh-3.2$ git clone ssh://[path]/caseissue
...
Receiving objects: 100% (4/4), done.
sh-3.2$ cd caseissue
sh-3.2$ ls
readme
Let's have a look at what is in the index:
sh-3.2$ git ls-files --stage
100644 a931371bf02ce4048b623c56beadb9a926138516 0 README
100644 418440c534135db897251cc3ceca362fe83c2117 0 readme
Sure enough, it has two files, differing only in case. Let's see what's in those files, and what's in the work-tree:
sh-3.2$ git show :0:README
I AM AN UPPERCASE FILE
sh-3.2$ git show :0:readme
i am a lowercase file
sh-3.2$ cat readme
i am a lowercase file
And our status:
sh-3.2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README
no changes added to commit (use "git add" and/or "git commit -a")
Depending on what we need to do, we may be able to do it while only knowing about the index, or we may need to work directly with the index, which is more painful.
1Technically, the frozen files' contents are stored in blob objects, their names are stored in tree objects, and the commits are commit objects that refer to tree objects that refer to the blob objects. But from the user point of view, the files are frozen into the commit, so we can just use that phrasing here.
2The index can actually be multiple different data files, and you can point Git at alternative index files and do all kinds of fancy tricks with this. That's how git stash
works, for instance. But "the" index is where Git builds the next commit you will make and for our purposes that's just the file .git/index
.
What to do about this if you don't need either file
Let's assume that you don't need to work with either file. If you need to work with both files in a case-sensitive manner, so that you can fuss with the contents of the two separate files name SeatSubstate.vue
and seatSubstate.vue
, you will, obviously, need to set up a case-sensitive file system. But whatever you're doing, we can assume that you don't need either file to do the job.
The trick to use here is to start by removing the one remaining file from your work-tree, and then ignore the fact that Git is telling you that you have two changes that are not staged for commit. That is, Git will tell you that you have removed both files.
sh-3.2$ rm readme
sh-3.2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: README
deleted: readme
no changes added to commit (use "git add" and/or "git commit -a")
Now, simply don't use git commit -a
at all, because that will stage both removals. Instead, work with the remaining files (in my case, none at all), do whatever you need to do, and stage—git add
—only those files that you modified, without touching either deleted file in any way.
You can now git commit
the result without affecting the two files that are missing from your work-tree, but still present in the new commit you make:
sh-3.2$ echo 'this file is independent of the READMEs' > newfile
sh-3.2$ git add newfile
sh-3.2$ git commit -m 'add new file'
[master 6d5d8fc] add new file
1 file changed, 1 insertion(+)
create mode 100644 newfile
sh-3.2$ git push origin master
Counting objects: 3, done.
...
2dee30f..6d5d8fc master -> master
Over on the other (case-sensitive file system) machine, after updating to this commit:
$ ls
newfile readme README
$ for i in *; do echo -n ${i}: && cat $i; done
newfile:this file is independent of the READMEs
readme:i am a lowercase file
README:I AM AN UPPERCASE FILE
So we're quite capable of working, on our Mac (or Windows!) system, with these commits: we just delete the unwanted files and carefully avoid staging the deletions.
What to do about this if you do need one of the files but don't need to change it
Now the problem is a little bit harder, because cannot hold both files with both spellings in our case-insensitive work-tree on our Mac or Windows system.
But we can pick and choose which file we get! Let's say we need the README
file. We can see that we got instead the readme
file above. So we'll remove the wrong one (well, we already did), and then:
sh-3.2$ git checkout -- README
sh-3.2$ ls
README newfile
sh-3.2$ cat README
I AM AN UPPERCASE FILE
If we need, instead, the lowercase one:
sh-3.2$ rm README
sh-3.2$ git checkout -- readme
sh-3.2$ ls
newfile readme
sh-3.2$ cat readme
i am a lowercase file
That is, we remove the wrong one, then use the grab one file from the index operation—git checkout -- path
—to get the one file with the one case that we do want. We can now work with this file. But we can't add or change it.
What if you need both files, or need to work on one of them?
If you need both at the same time with the fancy naming, you're in trouble, because your OS literally can't do that—at least, not on this file system; you'll need to create a case-sensitive file system, after which this whole problem goes away. But if you need just one at a time, to make some sort of change, that's something we can manage, albeit very awkwardly.
First, let's note that you can get one or both files' contents easily enough:
sh-3.2$ git show :README
I AM AN UPPERCASE FILE
sh-3.2$ git show :readme
i am a lowercase file
(Side note: the strings :0:README
and :README
mean exactly the same thing to git show
: get the file from index slot zero under path name README
. You can redirect the output from git show
to any file name you like, so that you can get both contents into two files with names your OS considers "different". You can use :README
or :0:README
as the argument to git show
. I'm not always consistent about whether I use the index number in the :
-prefixed form here. The reason there is a :0:
form is that there are also stage 1, 2, and 3 slots in the index, used only during merging. That is, if there is a :1:README
in the index, that's the merge base copy of README
; you will have this during a conflicted merge.)
As we saw above, you can also remove the work-tree file and use git checkout -- <path>
to get one of them, with your chosen case, into your work-tree with the same case. Unfortunately, if you want to modify and re-add the file, this doesn't always work:
sh-3.2$ rm readme
sh-3.2$ git checkout -- README
sh-3.2$ echo UPPERCASE IS LIKE SHOUTING >> README
sh-3.2$ git add README
sh-3.2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README
Yikes! It seems as though Git has decided that the README
file in the work-tree should update the stage-zero readme
file in the index! And sure enough, that's exactly what Git did:
sh-3.2$ git show :0:README
I AM AN UPPERCASE FILE
sh-3.2$ git show :0:readme
I AM AN UPPERCASE FILE
UPPERCASE IS LIKE SHOUTING
So now we have to resort to the tool that lets us write directly to the index. First, let's erase this change and get back to the "clean-ish" state where we have no work-tree copy. NOTE: if your actual work is more complicated than mine, you may want to save all of it somewhere else before git reset
wipes it out!
sh-3.2$ git reset --hard
HEAD is now at 6d5d8fc add new file
sh-3.2$ rm readme
sh-3.2$ git status --short
D README
D readme
The --short
output here, which has the D
character in the second position, shows that both files are missing from the work-tree, but that the index copy matches the HEAD
copy. So now we can get the file we want, whichever one that is—I'll pick the uppercase one again since it went wrong last time:
sh-3.2$ git checkout -- README
sh-3.2$ cat README
I AM AN UPPERCASE FILE
Now we use the normal computer tools to work with the file:
sh-3.2$ echo UPPERCASE IS LIKE SHOUTING >> README
When we need to add it back, though, we must use git hash-object -w
and git update-index
:
sh-3.2$ blob=$(git hash-object -w README)
sh-3.2$ echo $blob
fd109721431e207046a4daefc9712f1424d7f38f
(the echo
here is just for illustration, to show that we got a hash ID). Now we need to make a correctly-formatted index entry, a la git ls-files --stage --full-name
. That is, we need the full path to the file, relative to the top of the tree. Since my README
and readme
files are in the top of the tree, in my case here that just means README
or readme
. For your example, where your two files were in src/view/callCenter/seatReport
, you would need to include that in the path name.
In any case, having written the blob object to the Git database, we now need to update the index entry:
sh-3.2$ printf '100644 %s 0\tREADME\n' $blob | git update-index --index-info
sh-3.2$ git status --short
M README
M readme
This shows that we have one change staged for commit—to README
—and one not, to readme
. Here's the longer git status
if you prefer it:
sh-3.2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme
More directly, we can use git show
to view what's in the index:
sh-3.2$ git show :README
I AM AN UPPERCASE FILE
UPPERCASE IS LIKE SHOUTING
sh-3.2$ git show :readme
i am a lowercase file
That's what we want! So now we can git commit
the result:
sh-3.2$ git commit -m 'annotate README'
[master ff51464] annotate README
1 file changed, 1 insertion(+)
sh-3.2$ git push origin master
Counting objects: 3, done.
...
6d5d8fc..ff51464 master -> master
Over on the Unix-like system:
$ for i in *; do echo -n ${i}: && cat $i; done
newfile:this file is independent of the READMEs
readme:i am a lowercase file
README:I AM AN UPPERCASE FILE
UPPERCASE IS LIKE SHOUTING
You can always use git hash-object -w
and git update-index --index-info
If your OS is incapable of spelling a file or path name the way Git's index spells it, you can still work with the files' contents, under whatever names you can use. Having done so, you can use git hash-object -w
to turn the contents into a frozen blob, ready for commit, then use git update-index --index-info
to write that blob hash into the index—at the desired staging slot, usually zero—under the path-name that Git needs.
What you give up in this process is the ability to use git status
sensibly, to use git add
on problematic file names, and to use git commit -a
at all. What Git needs to make this more convenient—though it will never be 100% convenient; for that, you need your OS to behave instead—is the ability to re-map Git index paths to (different) local OS paths, in both directions: an index file named IP, for some index path IP, should not be assumed to have the same name in the work-tree, but rather its mapped name. The mapped name must map uniquely back to the index path. (That is, the mapping should be a bijection on paths.)
This is needed not only for case-folding issues but also for Unicode issues: MacOS stores file names in one form, having normalized them, while Linux allows storing file names in each form. A file named agréable
can have two names on Linux, but only one on MacOS.
来源:https://stackoverflow.com/questions/54490905/changes-not-staged-for-commit-even-after-git-commit-am-b-c-origin-has-a-file