Git:协调 Windows 中具有重复名称(大小写不同)的两个文件夹,同时保留历史记录

Git: Reconcile Two Folders with Duplicate Name (Cased Different) in Windows While Preserving History

我最近发现我的解决方案中有几个文件夹在 Git 中有两个不同的路径(GitHub 显示两个单独的文件夹),一个是 FooBar 和另一个是 Foobar。这是因为有些文件是以前者的文件夹名称作为路径注册的,有些是以后者命名的。

这是通过配置 Git 不忽略大小写在本地(在 Windows 中)发现的:git config core.ignorecase false

我尝试通过删除整个文件夹、提交、然后重新添加文件夹并再次提交来解决此问题。这解决了问题,但是路径更改的文件丢失了它们的 Git 历史记录。 运行 gitk 针对这些文件的新路径只显示了一次提交。 运行 gitk 违背他们的老路,暴露了他们的全部历史。

下一步:使用git mv移动文件:

git mv Foobar/file.txt FooBar/file.txt

这会产生错误:

fatal: destination exists, source=Foobar/file.txt, destination=FooBar/file.txt

如果我先尝试删除文件,当然 Git 会抱怨源文件不存在。

然后我发现如果您将 -f 添加到 mv 命令,Git 不会抱怨目的地已经存在。然而,在提交重命名之后,gitk 显示历史无论如何都被切断了!

我什至尝试过 three step dance described here,但这只是 -f 的另一种方式。结果相同。

基本上我只想在不区分大小写的操作系统中以某种方式将文件从 Foobar/file.txt 移动到 FooBar/file.txt,同时保留 Git 历史记录。这可能吗?

真正的问题没有简单的解决方案。

在 Git 中,文件 没有 历史记录。提交有历史——或者更准确地说,提交 历史。这就是所有的历史。对于 Git 到 "follow" 一个文件,如 git log --follow <path>,Git 查看 提交 ,一次一个,比较每个提交其父提交。

如果父子之间的差异显示父包含一个名为 parent/path/to/pfile 的文件,子包含一个名为 child/path/to/cfile 的文件和 内容 在这两个提交中,这两个文件中的一个是 "sufficiently similar"(这里必须满足几个条件),然后,在 Git 的 "eyes" 中,父子转换表示重命名该文件的。所以在那一点上,一直在寻找 child/path/to/cfilegit log --follow 开始寻找 parent/path/to/pfile.

没有--followgit log不做这个特殊的"find a rename"操作...一般来说,Git认为任何字节级的任何路径名差异代表不同的文件。换句话说,不会发生大小写折叠和 UTF-8 规范化。例如,考虑单词 schön,它可以表示为 s c h ö ns c h o 合并-¨ n。我们可以在 Linux 框上使用这两个不同的 UTF-8 样式名称创建两个 不同的 文件。 运行 ls 将显示两个名称相同的文件:

$ cat umlaut.py
import os
p1 = u'sch\N{latin small letter o with diaeresis}n'
p2 = u'scho\N{combining diaeresis}n'
os.close(os.open(p1.encode('utf8'), os.O_CREAT, 0o666))
os.close(os.open(p2.encode('utf8'), os.O_CREAT, 0o666))
$ python umlaut.py
$ ls
schön  schön  umlaut.py

Git 非常乐意分别存储这两个文件。然而,MacOS 拒绝允许两个文件共存,就像 Windows 一样——就此而言,默认情况下 MacOS 也拒绝允许两个文件 [=32] =] 和 FooBar 共存。

使 Git 将文件存储在新的字节序列下的新提交中,并且历史记录 保留的,这不是您 的历史记录希望保留。但是已经存储库中的历史已经不是您想要保留的历史。

实际上,您可能应该只重命名 Git 眼中的文件——这对您 OS 眼中的文件名没有影响; FooBarFoobar 在这里 是同一个 名字——继续做事。您的替代方法是通过复制(稍作修改)每个 "bad" 提交到新的和改进的 "good" 提交。但这意味着让 每个使用 repo 的人 从 "bad old repo" 切换到 "new and improved good repo".