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

Nic*_*tro 1 git merge file-rename git-merge

此问题使用 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
Run Code Online (Sandbox Code Playgroud)

您将遇到合并冲突,但不会出现冲突的块。也就是说,您应该得到的唯一冲突/错误是:

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.
Run Code Online (Sandbox Code Playgroud)

git status --porcelain会显示:

UD hw.h
A  hw.hpp
Run Code Online (Sandbox Code Playgroud)

通常,合并时,最好将检测重命名的阈值设置得足够低,以便检测到重命名。例如,有人建议 5% 。就我而言,我正在进行大规模合并(>1000 个文件和大约 9m LOC),并且我必须将重命名阈值提高到足够高以避免任何“误报”;实际上降低了百分之一,并且我得到了大量错误检测的重命名(我知道,重复的代码很糟糕)。按照我最终使用的值,我只得到了一小部分错过的重命名,这似乎是一个更好的选择。

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

tor*_*rek 5

用于此目的的工具有点笨拙,但它们在那里。

\n\n

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

\n\n

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

\n\n

如果 Git正确检测到重命名,则所有三个版本都将位于索引中的同一个名称下。既然没有,他们就不是:我们需要两个名字。(在“Git 认为它正确合并”的情况下,文件的合并基础版本根本不在索引中,我们必须以其他方式检索它。)您的案例中的两个名称是hw.hhw.hpp,所以现在我们这样做:

\n\n
$ git show :1:hw.h > hw.h.base    # extract base version\n$ git show :2:hw.h > hw.h         # extract ours\n$ mv hw.hpp hw.h.theirs           # move theirs into place\n
Run Code Online (Sandbox Code Playgroud)\n\n

(重命名并不是绝对必要的,它只是为了帮助保持一切正常并很好地说明。)

\n\n

现在我们想要将一个文件与以下内容合并git merge-file

\n\n
$ git merge-file hw.h hw.h.base hw.h.theirs\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将使用您的配置merge.conflictStyle,以便合并文件中的内容看起来就像您所期望的那样,只是冲突行上的标签有点不同。我已经diff3设置了,所以我得到:

\n\n
$ cat hw.h\n<<<<<<< hw.h\n//Boofar\n||||||| hw.h.base\n//Hello world!\n=======\n//Foobar\n>>>>>>> hw.h.theirs\n
Run Code Online (Sandbox Code Playgroud)\n\n

您现在可以像往常一样解决这个问题,rm额外的.base.theirs文件,git add最终结果,git rm --cached hw.hppgit commit。(这取决于您何时:在提交之前的任何时间点git rm --cached hw.hpp执行此操作都是安全的,但一旦完成,您将无法再从索引中获取“他们的”;请参见下文。)

\n\n

请注意,“我们的”和“他们的”版本也可以通过git show HEAD:path和获得git show MERGE_HEAD:path。要获得没有索引的基本版本,我们必须运行git merge-base HEAD MERGE_HEAD以查找其哈希 ID(然后假设还有一个合并基础1)和git show <hash>:path。如果 Git 认为它已经正确完成了合并,那么我们必须这样做。

\n\n

另请注意,如果您确实想要\xe2\x80\x94我想只有当您想使用您拥有的其他一些工具时这才是正确的,这需要它\xe2\x80\x94您可以使用它来随机排列git update-index条目在索引中,移至hw.hppslot-3 ,hw.h以便它确实显示为“他们的”,并在 中以这种方式显示git status。对于这个特定的例子:

\n\n
 $ printf \'100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\\thw.h\' |\n     git update-index --index-info\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的哈希值来自git ls-files --stage,并且是 的哈希值hw.hpp。(您需要第二步来删除hw.hpp索引条目。)

\n\n
\n\n

1用于git merge-base --all查找所有合并碱基。如果有多个合并库,您可以任意选择一个(这就是这样-s resolve做的),或者尝试将所有合并库合并到一个虚拟合并库中。要合并两个合并基础,您需要找到它们自己的合并基础,然后使用该合并基础将两个基础合并,就好像它们是分支提示一样。根据需要递归和迭代\xe2\x80\x94,这就是 Git 使用默认-s recursive策略\xe2\x80\x94 所做的事情,直到您拥有该文件的单个合并基础版本。

\n