我最近发现在我的解决scheme中有几个文件夹在Git中有两个不同的path(GitHub显示了两个单独的文件夹),一个是FooBar
,另一个是Foobar
。 这是因为有些文件是以前面的文件夹名称作为path注册的,有些则是后者。
这是在本地(在Windows中)通过configurationGit不忽略大小写发现的: git config core.ignorecase false
我试图通过删除整个文件夹,提交,然后重新添加文件夹并再次提交来解决这个问题。 这解决了这个问题, 但是修改了path的文件却失去了Git History 。 对这些文件的新path运行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
显示历史logging已被切断!
我甚至试图做这里描述的三步跳舞,但这只是另一种做-f
。 同样的结果。
基本上我只是想在一个不区分大小写的操作系统中以某种方式将文件从Foobar/file.txt
移动到FooBar/file.txt
,同时保留Git历史logging。 这可能吗?
真正的问题没有简单的解决方案。
在Git中,文件没有历史记录。 承诺有历史 – 或者更确切地说,承诺是历史。 这就是所有的历史。 为了让Git“跟随”一个文件,就像在git log --follow <path>
,Git会git log --follow <path>
查看提交 ,并将每个提交与其父提交进行比较。
如果parent和child之间的差异显示父级包含名为parent/path/to/pfile
的文件,并且子级包含名为child/path/to/cfile
的文件以及这两个文件的内容 ,则在这两个提交中,is “足够相似”(这里有几个条件),那么在Git的“眼睛”中,父母到孩子的转换代表了该文件的重命名。 所以在这一点上,一直在寻找child/path/to/cfile
git log --follow
开始寻找parent/path/to/pfile
。
如果没有 – --follow
, git log
就不会执行这个特殊的“查找重命名”操作…一般来说,Git认为任何字节级差异的路径名代表不同的文件。 换句话说,不会发生案例折叠和UTF-8规范化。 考虑,例如, schön
这个词,可以表示为s
ö
或s
c
h
o
组合。 我们可以在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默认也拒绝允许Foobar
和FooBar
共存。
让Git以新的字节序列在新的提交中存储文件,并保存历史记录,这不是你想要保存的历史记录。 但是已经存在的历史已经不是你想要保存的历史了。
在实践中,你可能应该只是重命名Git的眼睛中的文件 – 这对你的操作系统眼中的文件名没有任何影响; FooBar
和Foobar
在这里是相同的名字,并继续下去。 另一种方法是将所有的历史记录重新写入到首次添加到存储库中的时间点,方法是将每个“坏”提交复制到一个新的和改进的“良好”提交中。 但是,这意味着让每个使用回购的人都从“旧的回购”转变为“新的和改进的回购”。