简体   繁体   English

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

[英]Git: Manually merge changes in an undetected rename

This question uses Git 2.7.0.windows.1 , so some of the git commands might be outdated. 这个问题使用Git 2.7.0.windows.1 ,因此某些git命令可能已过时。

If my git merge command doesn't detect a renamed file, how can I tell git to manually merge the changes in two files that are supposed to be a single file, without starting the merge over and using a lower rename threshold? 如果我的git merge命令没有检测到重命名的文件,我如何告诉git手动合并应该是单个文件的两个文件中的更改,而无需重新开始合并并使用较低的重命名阈值?

Reproduction Steps: 复制步骤:

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

You will get merge conflicts, but no conflicted chunks. 您将获得合并冲突,但不会有冲突的块。 That is, the only conflict/error you should get is: 也就是说,您应该获得的唯一冲突/错误是:

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.

And git status --porcelain will show: git status --porcelain将显示:

UD hw.h
A  hw.hpp

Normally, when merging, it's ideal to set the threshold for detecting renames low enough that renames are detected. 通常,合并时,最好将检测重命名的阈值设置得足够低,以至于无法检测到重命名。 Some people recommend 5% , for example. 例如, 有人建议5% In my case, I'm doing a massive merge (>1000 files and about 9m LOC), and I had to raise the rename threshold high enough to avoid any "false positives"; 就我而言,我正在进行大量合并(> 1000个文件和大约900万个LOC),并且我不得不将重命名阈值提高到足够高的水平,以避免出现任何“误报”。 literally one percent lower and I got a huge swath of falsely-detected renames (I know, duplicated code sucks). 实际降低了百分之一,而且我得到了大量错误检测到的重命名(我知道,重复的代码很烂)。 At the value I wound up using, I get only a small handful of missed renames, which seems like a better option. 以我最终使用的价值,我只得到了很少的错过的重命名,这似乎是一个更好的选择。

TL;DR lowering the rename threshold is not an option for me; TL; DR降低重命名阈值不是我的选择; how can I, without starting the merge over, tell git to consider hw.h and hw.hpp to be a single file (with conflicts), rather than two files as shown above? 如何在不开始合并的情况下告诉git将hw.hhw.hpp视为单个文件(有冲突),而不是如上所述的两个文件?

The tools for this are a bit klunky, but they are there. 用于此的工具有些笨拙,但它们那里。

You need to be sure that the merge itself stops before committing. 您需要确保合并本身在提交之前停止。 In your case this happens automatically. 您的情况会自动发生。 For trickier merges, where Git thinks it's doing it correctly but is not, you would add --no-commit , but this then affects the next few steps. 对于棘手的合并,Git认为它可以正确执行,但实际上并没有,您可以添加--no-commit ,但这会影响接下来的几个步骤。 We'll ignore that problem for now. 我们暂时将忽略该问题。

Next, you need to get all three versions of the file in question. 接下来,您需要获取该文件的所有三个版本。 Since Git stopped with a conflict, we're in good shape: the three versions are all accessible through the index. 由于Git因冲突而停止,因此我们处于良好状态:这三个版本均可通过索引访问。 Remember that the three versions we care about are merge base , --ours , and --theirs . 请记住,我们关心的三个版本是merge base ,-- --ours--theirs

If Git had detected the rename correctly, all three versions would be in the index under a single name. 如果Git 正确检测到重命名,则所有三个版本将以单个名称在索引中。 Since it did not, they are not: we need two names. 既然没有,他们就不是:我们需要两个名字。 (With the "Git thinks it did the merge correctly" case, the merge base version of the file is not in the index at all, and we have to retrieve it some other way.) The two names in your case here are hw.h and hw.hpp , so now we do this: (在“ Git认为它正确合并的情况下”,该文件的合并基础版本根本不在索引中,我们必须以其他方式检索它。)在这种情况下,这两个名称是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

(The renaming is not strictly necessary, it's just to help keep it all straight and nicely illustrated.) (重命名并不是严格必须的,它只是为了使它们保持整洁并得到很好的说明。)

Now we want to merge the one file with git merge-file : 现在我们要用git merge-file

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

This uses your configured merge.conflictStyle so that what's in the merged file looks just as you would expect, except that the labels on the conflicted lines are a bit different. 这将使用您配置的merge.conflictStyle以便合并文件中的内容与您期望的一样,除了冲突行上的标签略有不同。 I have diff3 set, so I get: 我设置了diff3 ,所以我得到:

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

You can now resolve this as usual, rm the extra .base and .theirs files, git add the final result, git rm --cached hw.hpp , and git commit . 现在,您可以解决此像往常一样, rm额外.base.theirs文件, git add最终的结果, git rm --cached hw.hpp ,和git commit (It's up to you when to git rm --cached hw.hpp : it's safe to do this at any point in time before the commit, but once done you can no longer get "theirs" from the index; see below.) (这取决于您何时使用git rm --cached hw.hpp :在提交之前的任何时间都可以安全地执行此操作,但是一旦完成,您将无法再从索引中获取“它们的”;请参见下文。)

Note that the "ours" and "theirs" versions are also available through git show HEAD:path and git show MERGE_HEAD:path . 注意,“我们的”和“他们的”版本也可以通过git show HEAD:pathgit show MERGE_HEAD:path To get at the base version without the index, we would have to run git merge-base HEAD MERGE_HEAD to find its hash ID (and then assume there's a single merge base as well 1 ), and git show <hash>:path . 为了获得没有索引的基本版本,我们必须运行git merge-base HEAD MERGE_HEAD来查找其哈希ID(然后假设还有一个合并基数1 ),以及git show <hash>:path This is what we must do if Git thinks it has done the merge correctly. 如果Git认为它正确完成了合并,这就是我们必须做的。

Note also that if you really want to—I imagine this would only be true if you wanted to use some other tool(s) you have, that require it—you can use git update-index to shuffle the entries around in the index, moving hw.hpp into slot-3 of hw.h so that it does show up as "theirs", and shows up that way in git status . 还要注意,如果您确实想(我想这只有在您想要使用其他一些需要它的工具时才是对的),您可以使用git update-index来随机整理git update-index中的条目,移动hw.hpp入插槽,3 hw.h ,以免被显示为“他们”,并显示了以这种方式git status For this particular example: 对于此特定示例:

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

The hash here came from git ls-files --stage and is the hash for hw.hpp . 这里的哈希值是从哪里来的git ls-files --stage ,是哈希hw.hpp (You need a second step to remove the hw.hpp index entry.) (您需要执行第二步来删除hw.hpp索引条目。)


1 Use git merge-base --all to find all merge bases. 1使用git merge-base --all查找所有合并库。 If there is more than one, you can either pick one arbitrarily (this is what -s resolve does), or try to merge all the merge bases into a virtual merge base . 如果有多个,则可以任意选择一个(这是-s resolve作用),也可以尝试将所有合并基础合并为虚拟合并基础 To merge two merge bases, you find their own merge base, and merge two bases as if they are branch tips, using that merge base. 要合并两个合并库,您可以找到它们自己的合并库,并使用该合并库合并两个库,就好像它们是分支提示一样。 Recurse and iterate as needed—this is what Git does with the default -s recursive strategy—until you have a single merge base version of the file. 根据需要进行递归和迭代-这是Git使用默认的-s recursive策略所做的-直到您拥有该文件的单个合并基础版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM