Git: 在未检测到的重命名中手动合并更改

Git: Manually merge changes in an undetected rename

这道题使用了Git 2.7.0.windows.1,所以一些git命令可能已经过时了。

如果我的 git merge 命令没有检测到重命名的文件,我如何告诉 git 手动合并两个应该是单个文件的文件的更改,而不启动合并并使用较低的重命名阈值?

复制步骤:

git init
echo "//Hello world!" > hw.h
git add . && git commit -m "Initial commit"
git checkout -b someBranch
mv hw.h hw.hpp
echo "//Foobar" > hw.hpp
git add . && git commit -m "Change hw to HPP & change content"
git checkout master
echo "//Boofar" > hw.h
git add . && git commit -m "Change content of hw"
git merge -X rename-threshold=100% someBranch

您会遇到合并冲突,但不会有冲突的块。也就是说,您应该得到的唯一 conflict/error 是:

CONFLICT (modify/delete): hw.h deleted in branchB and modified in HEAD. Version HEAD of hw.h left in tree.
Automatic merge failed; fix conflicts and then commit the result.

git status --porcelain会显示:

UD hw.h
A  hw.hpp

通常,合并时,最好将检测重命名的阈值设置得足够低,以便检测到重命名。 Some people recommend 5%,例如。就我而言,我正在进行大规模合并(>1000 个文件和大约 900 万行代码),并且我必须将重命名阈值提高到足够高以避免任何 "false positives";实际上降低了百分之一,我得到了大量错误检测的重命名(我知道,重复的代码很糟糕)。根据我最终使用的值,我只得到了一小部分遗漏的重命名,这似乎是一个更好的选择。

TL;DR 降低重命名阈值不是我的选择;我如何在不重新开始合并的情况下告诉 git 将 hw.hhw.hpp 视为单个文件(有冲突),而不是如上所示的两个文件?

这方面的工具有点笨拙,但它们 在那里。

您需要确保合并本身在提交之前停止。在您的情况下,这是自动发生的。对于更棘手的合并,Git 认为它正确执行但不是,您可以添加 --no-commit,但这会影响接下来的几个步骤。我们暂时忽略这个问题。

接下来,您需要获取相关文件的所有三个版本。由于 Git 因冲突而停止,我们的状态很好:三个版本都可以通过索引访问。记得我们关心的三个版本是merge base--ours--theirs.

如果 Git 正确检测到重命名,所有三个版本将在索引中以 单一 名称命名。既然没有,他们就不是:我们需要两个名字。 (在 "Git thinks it did the merge correctly" 情况下,文件的合并基础版本根本不在索引中,我们必须以其他方式检索它。)您的情况下的两个名称是 hw.hhw.hpp,所以现在我们这样做:

$ git show :1:hw.h > hw.h.base    # extract base version
$ git show :2:hw.h > hw.h         # extract ours
$ mv hw.hpp hw.h.theirs           # move theirs into place

(重命名不是绝对必要的,只是为了帮助保持一切正常和漂亮的插图。)

现在我们要合并一个文件 git merge-file:

$ git merge-file hw.h hw.h.base hw.h.theirs

这会使用您配置的 merge.conflictStyle,以便合并文件中的内容看起来与您预期的一样,只是冲突行上的标签略有不同。我设置了 diff3,所以我得到:

$ cat hw.h
<<<<<<< hw.h
//Boofar
||||||| hw.h.base
//Hello world!
=======
//Foobar
>>>>>>> hw.h.theirs

您现在可以照常解决此问题,rm 额外的 .base.theirs 文件,git add 最终结果,git rm --cached hw.hpp,以及 git commit。 (由您决定何时 git rm --cached hw.hpp:在提交前的 任何 时间点执行此操作是安全的,但一旦完成,您将无法再获得 "theirs"来自索引;见下文。)

请注意,"ours" 和 "theirs" 版本也可通过 git show HEAD:pathgit show MERGE_HEAD:path 获得。要获得没有索引的基础版本,我们必须 运行 git merge-base HEAD MERGE_HEAD 找到它的哈希 ID(然后假设还有一个合并基础 1), 和 git show <hash>:path。如果 Git 认为它已正确完成合并,这就是我们必须做的。

另请注意,如果您真的想要——我想这只有在您想要使用您拥有的其他需要它的工具时才是正确的——您可以使用 git update-index 来随机播放索引中的条目,将 hw.hpp 移动到 hw.h 的插槽 3 中,以便它 确实 显示为 "theirs",并以这种方式显示在 git status。对于这个特定的例子:

 $ printf '100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\thw.h' |
     git update-index --index-info

此处的散列来自 git ls-files --stage,是 hw.hpp 的散列。 (您需要第二个步骤来删除 hw.hpp 索引条目。)


1使用git merge-base --all找到所有合并碱基。如果不止一个,你可以任意选择一个(这就是-s resolve所做的),或者尝试将所有合并基地合并成一个虚拟合并基地。要合并两个合并基础,您找到他们自己的合并基础,并使用该合并基础合并两个基础,就好像它们是分支提示一样。根据需要递归和迭代——这就是 Git 对默认 -s recursive 策略所做的——直到你有一个文件的单一合并基础版本。