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/cfile
的 git log --follow
开始寻找 parent/path/to/pfile
.
没有--follow
,git log
不做这个特殊的"find a rename"操作...一般来说,Git认为任何字节级的任何路径名差异代表不同的文件。换句话说,不会发生大小写折叠和 UTF-8 规范化。例如,考虑单词 schön
,它可以表示为 s
c
h
ö
n
或 s
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 眼中的文件名没有影响; FooBar
和 Foobar
在这里 是同一个 名字——继续做事。您的替代方法是通过复制(稍作修改)每个 "bad" 提交到新的和改进的 "good" 提交。但这意味着让 每个使用 repo 的人 从 "bad old repo" 切换到 "new and improved good repo".
我最近发现我的解决方案中有几个文件夹在 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/cfile
的 git log --follow
开始寻找 parent/path/to/pfile
.
没有--follow
,git log
不做这个特殊的"find a rename"操作...一般来说,Git认为任何字节级的任何路径名差异代表不同的文件。换句话说,不会发生大小写折叠和 UTF-8 规范化。例如,考虑单词 schön
,它可以表示为 s
c
h
ö
n
或 s
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 眼中的文件名没有影响; FooBar
和 Foobar
在这里 是同一个 名字——继续做事。您的替代方法是通过复制(稍作修改)每个 "bad" 提交到新的和改进的 "good" 提交。但这意味着让 每个使用 repo 的人 从 "bad old repo" 切换到 "new and improved good repo".