Git和文件重命名和替换
Posted
技术标签:
【中文标题】Git和文件重命名和替换【英文标题】:Git and file renaming and replacing 【发布时间】:2014-11-06 07:46:03 【问题描述】:使用 git 重命名通常没有问题,但我遇到了一个非常困难的问题,我正在努力解决。
由于各种原因,我有一个文件dir1/file
。由于一些很久以前的决定,它在完全错误的地方,需要转移到dir2/file
。
但是有很多代码需要更改,并且由于各种原因,我们不得不将文件在新位置和旧位置保留一段时间。
因此,自然(ish)的方法是这样做:
git mv dir1/file dir2/file
git commit -a
到目前为止一切顺利:
> git diff master --name-status --find-renames
R100 dir1/file dir2/file
那么我们就这样做
ln -s ../dir2/file dir1/file
git commit -a
但是会发生这种情况
> git diff master --name-status --find-renames
A dir2/file
T dir1/file
如果有人在 master 上更改 dir1/file
并且我尝试将其拉出,我会被告知与 dir1/file1
存在合并冲突,并且 dir2/file1
保持不变。我从阅读 git 跟踪内容的其他帖子中想到,但它似乎在跟踪文件名和内容。并且完全忽略了内容已经移动的事实。
那么到底如何让 git 识别出我实际上重命名了一个文件,然后添加了一个恰好与旧文件同名的新文件?
注意:我不想将其作为多次推送来执行。有几个像这样的文件受到影响,并且有人同时对其中一个文件进行更改的可能性非常高,并且不能保证他们能够进行拉取以获取重命名,然后拉取以获取软链接.
加法示例。我正在从 python 模块 __init__.py
中删除一个函数,它不应该存在,__init__.py
应该是空的。这也没有被发现为重命名。即使新文件的内容与原始 __init__.py
的内容有 99% 的相同,新的 __init__.py
的内容与旧的内容也有 0% 的相同。在我添加同名文件之前一切都很好。
【问题讨论】:
【参考方案1】:事实上,Git 会跟踪内容而不是——或者更确切地说,我们应该说“除了”——名称。 diff 出错是因为git diff
(必然)尝试映射名称并比较两个单独提交的内容(或者一个提交和当前工作目录,或者一个提交和当前索引等,但这些只是变体“比较两个提交”的主题)。
更具体地说,当git diff
比较树1T1
和T2
,它默认情况下,重命名的唯一候选是那些在T1
中存在某些文件名但在T2
中不存在的文件名, T2
中存在其他一些(不同的)文件名,但 T1
中不存在。
因此,当您进行第一次提交时,您有两个提交——我们称之为 A 和 B——有两棵树,其中 dir1/file1
从 A“消失”,dir2/file2
出现在 B 中。这是重命名的候选者-detection,并且由于文件内容 100% 相同,git 很容易发现重命名并为您提供 R100
diff 输出。
当您进行第二次提交时,您添加了带有第三棵树的提交 C。比较 B 和 C 效果很好:dir2/file
出现在两者中,新的符号链接 dir1/file
只出现在 C 中,并且这对的差异输出也很好。比较 A 和 C 时出现问题:现在 dir1/file1
出现在两者中,而 dir2/file2
仅出现在 C 中,git diff
没有意识到存在重命名候选。
有一个标志,--find-copies-harder
——或者您可以多次指定-C
——这(毫不奇怪)使复制/重命名检测代码更加工作。在这种情况下,git 将考虑“看起来未更改”的文件(在两棵树中具有相同的名称)可能已被复制或重命名为另一个“看起来是新的”文件(存在于第二棵树中但不存在于第一棵树中)的可能性。默认情况下不启用此功能,因为完全通用版本的计算量非常大。
不幸的是,在计算git merge
的差异集时,无法控制使用的差异选项。合并命令设置一些默认值(-M50% 等)并进行一些差异,并且不允许您设置 --find-copies-harder
。因此,即使这适用于手动 git diff
,它也无法解决您的合并冲突。
请注意,当您进行合并时,2 git 只计算两组差异:从合并基础3 到当前的 HEAD
,以及从合并基础到合并提交(git 合并提交,而不是分支:当该提交是分支的尖端时,结果合并该分支的事实是一种“故意巧合”)。因此,可以将重命名作为一次提交,将符号链接作为第二次提交,但是要让git merge
“看到”重命名,您还必须执行两个单独的git merge
s。这不是特别令人愉快,但要解决这个问题,你必须让 git 的 diff
机器更智能,这样它至少可以弄清楚文件类型更改会增加找到重命名的机会,如果它“发现复制/重命名有点困难”。
(请注意,将其添加到 diff 机制将解决这两个问题 - git diff 看不到重命名,而 git merge 看不到重命名 - 一次全部解决。)
1这里的“树”是指完整的文件树,而不是 git 的 tree
对象。
2更具体地说,这是双父合并的情况。章鱼合并的处理方式不同。我还没有深入了解章鱼合并的内脏,也不能再多说什么了。
3merge-base 取决于要合并的两个(或多个)commit,如果有多个 merge-base,则使用默认 (recursive
) 策略使事情复杂化候选人,git 计算一个“虚拟合并基础”,它不一定与任何实际提交相同。细节不是我可以在这里正确解释的:我知道一般的想法,但不知道 git 中的细节,无论如何它很少重要并且与您的问题没有直接关系。如果您想阅读更多内容,有一个相当不错的示例 here,尽管该示例使用了一些类似 Clearcase 的术语。
【讨论】:
即使在 7 年后,这也是一个深入且非常有用的答案。谢谢!以上是关于Git和文件重命名和替换的主要内容,如果未能解决你的问题,请参考以下文章