首页 > 解决方案 > 即使在 git commit -am b/c origin 有一个带有反大写文件名的文件后,“更改未暂存以进行提交”

问题描述

问题:同一个目录下两个不同名称的两个文件,一开始我不知道。所以看到这个我很惊讶

git commit -am "why"
On branch tmp
Changes not staged for commit:
    modified:   src/view/callCenter/seatReport/SeatSubstate.vue

然后我发现 origin在路径中有SeatSubstate.vueseatSubstate.vuesrc/view/callCenter/seatReport

但是在我的mac上

ls src/view/callCenter/seatReport/
...     seatSubstate.vue /* did NOT show SeatSubstate.vue only seatSubstate.vue */

我知道有关于如何在 Git 中提交仅区分大小写的文件名更改的讨论?

但是我还是不明白为什么git不能提交这个文件。

其次,我该如何解决这个问题?例如,在那次讨论中提到了许多回答git mv,但我不确定git mv能否解决我的问题。

- - - 更新 - - -

我突然意识到我的 mac(确切地说是我的 HD)不区分大小写(APFS),请参阅https://apple.stackexchange.com/questions/71357/how-to-check-if-my-hd-is -区分大小写或不区分

在此处输入图像描述

通常它应该意味着 SeatSubstate.vue 和 seatSubstate.vue 是同一个文件,但是 git 不知何故将它们变成了 2 个不同的文件并导致了麻烦。git mv似乎可以解决问题,但我不确定 100%。

请参阅在 Git 中更改文件名的大小写

标签: gitcase-sensitivegit-remote

解决方案


正确定义问题

Git总是能够存储——在提交中,在 Git 的索引中,即——在同一个目录中的两个不同名称大小写(例如,两者READMEreadme)下的两个文件,因为 Git 不会将文件存储在操作系统目录中全部。文件要么在提交中冻结,1这意味着无论它们是在 Linux、Windows 还是 MacOS 或任何其他系统上,它们都保留它们的形式,或者它们在 Git 的索引中,这实际上只是一个数据文件。2

出现问题的原因是您作为操作 Git 的人类想要使用操作系统提供的文件系统,您的计算机在其中以正常的日常形式存储文件,以便计算机的其余部分也可以使用它们。这不是一个不合理的要求——Git 的内部文件以 Git 专用的内部形式存储,只有 Git 可以使用。你需要能够使用 Git完成某事,而不仅仅是整天玩 Git。

MacOS 能够提供区分大小写的文件系统(可以同时保存README并保存readme在同一目录中),但默认情况下不这样做。所以,要么完全不使用 MacOS,要么使用这种能力,有人——不是你——做了这样的事情:

然后我发现 origin在路径中有SeatSubstate.vueseatSubstate.vuesrc/view/callCenter/seatReport

换句话说,您在一些现有的commit中都有这两个文件。正如我们刚才所说,Git 完全有能力处理这个问题。不是你的操作系统。

因此,如果您运行git checkout选择该提交,Git 会将这两个文件复制到您的索引中,该索引现在具有拼写和. 它还将两个文件(两种拼写!)复制到您的工作树中,但是您的操作系统只能保存一个拼写,因此一个文件会擦除另一个文件,而您只剩下一个带有一种拼写的文件。SeatSubstate.vueseatSubstate.vue

当 Git 将索引的文件及其内容与工作树文件及其内容进行比较时,Git 将:

  • 看到,根据索引,有两个文件;
  • 尝试将每个索引文件与 Git 打开该名称时获取的工作树文件进行比较;
  • 抱怨其中一个被修改了。

这是一个示例,我通过在 Unix-y 系统上创建一个存储库并为其提供两个文件READMEreadme,具有不同的内容,然后将其克隆到 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

让我们看一下索引中的内容:

sh-3.2$ git ls-files --stage
100644 a931371bf02ce4048b623c56beadb9a926138516 0       README
100644 418440c534135db897251cc3ceca362fe83c2117 0       readme

果然,它有两个文件,只是大小写不同。让我们看看这些文件有什么,以及工作树中有什么:

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

还有我们的状态:

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

根据我们需要做什么,我们可能只知道索引就可以做到,或者我们可能需要直接与索引一起工作,这样比较痛苦。


1从技术上讲,冻结文件的内容存储在blob 对象中,它们的名称存储在树对象中,提交是指引用 blob 对象的树对象的提交对象但从用户的角度来看,文件被冻结在提交中,所以我们可以在这里使用该措辞。

2索引实际上可以是多个不同的数据文件,您可以将 Git 指向其他索引文件并用它做各种花哨的技巧。例如,这就是git stash工作原理。但是“the”索引是 Git 构建您将进行的下一次提交的地方,对我们而言,这只是 file .git/index


如果您不需要任何一个文件,该怎么办

假设您不需要使用任何一个文件。如果您需要以区分大小写的方式处理这两个SeatSubstate.vue文件,这样您就可以对两个单独文件名的内容大惊小怪seatSubstate.vue,显然,您需要设置一个区分大小写的文件系统。但是无论您在做什么,我们都可以假设您不需要任何一个文件来完成这项工作。

这里使用的技巧是首先从你的工作树中删除一个剩余的文件,然后忽略 Git 告诉你有两个未暂存提交的更改这一事实。也就是说,Git 会告诉你你已经删除了这两个文件。

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

现在,根本不要使用,因为这将进行两次删除。相反,使用剩余的文件(在我的情况下,根本没有),做任何你需要做的事情,并且暂存-<code>git add-仅那些你修改过的文件,而不以任何方式接触任何一个已删除的文件。git commit -a

您现在可以git commit在不影响工作树中缺少但仍存在于您所做的新提交中的两个文件的情况下获得结果:

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

更新到此提交后,在另一台(区分大小写的文件系统)机器上:

$ 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

因此,我们完全有能力在我们的 Mac(或 Windows!)系统上使用这些提交:我们只是删除不需要的文件并小心避免暂存删除。

如果您确实需要其中一个文件不需要更改它,该怎么办

现在问题有点难了,因为在我们的 Mac 或 Windows 系统上的不区分大小写的工作树中不能同时保存两个文件。

但是我们可以选择我们得到的文件!假设我们需要该README文件。我们可以看到我们得到了readme上面的文件。所以我们将删除错误的(好吧,我们已经这样做了),然后:

sh-3.2$ git checkout -- README
sh-3.2$ ls
README  newfile
sh-3.2$ cat README 
I AM AN UPPERCASE FILE

相反,如果我们需要小写字母:

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

也就是说,我们删除错误的文件,然后使用从索引操作中抓取一个文件——<code>git checkout --path——来获取我们想要的一种情况的文件。我们现在可以使用这个文件。但我们不能添加或更改它。

如果您需要两个文件,或者需要处理其中一个文件怎么办?

如果你同时需要这两种命名方式,那你就有麻烦了,因为你的操作系统实际上不能这样做——至少,在这个文件系统上是这样。您需要创建一个区分大小写的文件系统,之后整个问题就会消失。但是,如果您一次只需要一个来进行某种更改,那是我们可以做到的,尽管很尴尬。

首先,请注意,您可以很容易地获取一个或两个文件的内容

sh-3.2$ git show :README
I AM AN UPPERCASE FILE
sh-3.2$ git show :readme
i am a lowercase file

(旁注:字符串:0:README:README含义完全相同git show:从路径名下的索引槽零获取文件README。您可以将输出重定向git show到您喜欢的任何文件名,这样您就可以将两个内容放入具有名称的两个文件中您操作系统认为“不同”。您可以使用:READMEor:0:README作为git show. 2,和索引中的 3 个插槽,仅在合并期间使用。也就是说,如果索引中有 a,那是 的合并基础副本;在冲突合并期间你将拥有它。)::0::1:READMEREADME

正如我们在上面看到的,您还可以删除工作树文件并使用您选择的案例git checkout -- <path>其中一个文件放入具有相同案例的工作树中。不幸的是,如果您想修改并重新添加文件,这并不总是有效:

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

哎呀!似乎 Git 已经决定工作树中的文件应该更新索引中README的阶段零文件!readme果然,这正是 Git 所做的:

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

所以现在我们必须求助于让我们直接写入索引的工具。首先,让我们删除这个更改并回到没有工作树副本的“干净”状态。 注意:如果您的实际工作比我的更复杂,您可能希望在将其全部清除之前将其保存在其他地方git reset

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

--short此处的输出D在第二个位置具有字符,表明工作树中缺少两个文件,但索引副本与副本匹配HEAD。所以现在我们可以得到我们想要的文件,无论是哪个——我会再次选择大写的,因为上次出错了:

sh-3.2$ git checkout -- README
sh-3.2$ cat README 
I AM AN UPPERCASE FILE

现在我们使用普通的计算机工具来处理文件:

sh-3.2$ echo UPPERCASE IS LIKE SHOUTING >> README

但是,当我们需要将其添加回来时,我们必须使用git hash-object -wand git update-index

sh-3.2$ blob=$(git hash-object -w README)
sh-3.2$ echo $blob
fd109721431e207046a4daefc9712f1424d7f38f

echo这里只是为了说明,表明我们得到了一个哈希 ID)。现在我们需要创建一个格式正确的索引条目,a la git ls-files --stage --full-name。也就是说,我们需要文件的完整路径,相对于树的顶部。由于我的READMEreadme文件位于树的顶部,在我的情况下,这只是意味着READMEor readme。对于您的示例,您的两个文件位于 中src/view/callCenter/seatReport,您需要将其包含在路径名中。

无论如何,在将 blob 对象写入 Git 数据库之后,我们现在需要更新索引条目:

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

这表明我们有一个变更为提交README——to——而一个不为readmegit status如果您愿意,可以使用更长的时间:

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

更直接地,我们可以使用git show来查看索引中的内容:

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

这就是我们想要的!所以现在我们可以git commit得到结果:

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

在类 Unix 系统上:

$ 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

您可以随时使用git hash-object -wgit update-index --index-info

如果您的操作系统无法按照 Git 索引的拼写方式拼写文件或路径名,您仍然可以使用可以使用的任何名称来处理文件的内容。完成此操作后,您可以使用将内容转换为冻结的 blob,准备提交,然后使用将该 blob 哈希写入索引 - 在所需的暂存槽处,通常为零 - 在 Git 需要的路径名下。git hash-object -wgit update-index --index-info

您在此过程中放弃的是git status明智地使用,用于git add有问题的文件名以及完全使用git commit -a的能力。Git 需要什么让这更方便——尽管它永远不会 100% 方便;为此,您需要您的操作系统来代替 - 能够在两个方向上将 Git 索引路径重新映射到(不同的)本地操作系统路径:一个名为IP的索引文件,对于某些索引路径IP,不应假定为在工作树中具有相同的名称,而是其映射名称。映射的名称必须唯一地映射回索引路径。(也就是说,映射应该是路径上的双射。)

这不仅适用于大小写折叠问题,也适用于 Unicode 问题:MacOS 以一种形式存储文件名,并对其进行规范化,而 Linux 允许以每种形式存储文件名。一个名为的文件agréable在 Linux 上可以有两个名称,但在 MacOS 上只有一个。


推荐阅读