合并两个分支,拆分一个目录

Merge two branches, splitting a directory

在我们的一个 git 存储库中,我们有两个分支,每个分支都在某些目录上工作,以至于它们大相径庭1。我们现在要合并两个分支,保留两个版本。

我试过重命名一个目录,使它们在磁盘上不重叠,但是当我合并分支时 git 知道它们最初都来自同一源,并且很有帮助 "moves" 文件从一个到另一个,伴随合并冲突。

我也尝试过使用 git merge -s ours branchname,然后使用 git checkout branchname -- directory/,但这看起来破坏了 "theirs" 分支的历史,使文件看起来像是突然出现的.理想情况下,我想保留对预合并分支中的文件进行修改的能力,合并能够找到文件的正确版本。

有没有办法告诉 git 合并两个分支但保持某些 files/directories 为 "separate",尽管共享来源?或者换句话说,有没有办法吐出一个文件的历史,这样 git 知道它在一个分支中移动但在另一个分支中没有移动?


1 这些是 documentation/test 目录,因此对代码重复的标准关注很少甚至不存在。

这里有坏消息,也有好消息。

Git 不关心(在某种程度上,例如,在打包文件的工作方式中有秘密的关心位,这也是我要提到的)关于路径名在提交。它只关心内容:文件中的位,以及放置这些内容的名称。除了父 ID 之外,每个提交都完全独立于它之前或(最终)之后的任何提交。因此,"files" 根本没有任何历史记录。

显然,文件 do 有历史记录,因为如果你比较两个提交(这是 git show 在显示提交时所做的),你会看到一个补丁从 "previous version of foo" 到 "new version of foo",您可以执行 "git blame foo" 之类的操作来查看历史记录。

Git 通过构建一个历史 每次你请求一个 ,使用内容来调和这两个对立面。如果您 运行 git show,或 git log -p,要查看更改内容,git 会根据内容立即重建历史记录。

在查找被移动/重命名的文件方面,git 使用了几种技巧中的一种或多种,​​具体取决于您的指导方式。您可以告诉 git diff(包括大多数获得差异的命令,其中包括合并操作)根本不要检查。这是最快的方法。

你可以告诉它使用一个主要是快速的(但仍然是 O(n2))算法,该算法只查看仅在两个提交之一中的路径名diff 正在比较。这是合并的默认方法(您可以通过配置 diff.renameLimit 将其配置为差异的默认方法,或者您可以使用 -M 选项提供它)。

或者,您可以告诉它使用慢速甚至非常慢的方法,使用 --find-copies(又名 -C)或 --find-copies-harder

默认的 mostly-fast 方法确实使用路径名,而 very-slow 方法则不使用。不过,两者都仍然依赖于内容。特别是,在复制或重命名检测方面,文件被视为 "the same",如果它们是 "at least 50% similar",或者您选择的与 -M and/or -C 参数 diff.

这既是好消息也是坏消息。本质上,每次你 git 比较两个提交——包括任何将来回顾这些提交的合并基础的合并——git 都会找到一些重命名,而不会找到其他一些重命名 and/or 副本,取决于你给它的标记和内容相似性。您可以在合并期间对检测值大惊小怪(-X rename-threshold 而不是 -M),但这里的控件非常粗糙。

(请注意,git blamegit log --follow 在尝试发现重命名时也会执行这种基于名称和内容的匹配。git log --follow 的算法仅在以下情况下有效及时向后移动,从当前路径到上一个路径,因此与 --reverse 组合时失败。)