所以,我有这个合并提交案例:
--D--E--
/ \
--A--B--C- ---H---
\ /
--F--G--
其中:
develop
分支feature
分支develop
分支中,从另一个功能分支合并feature
到develop
问题是,当我将feature
合并到develop
时,我们丢失了在分支之前引入的一些代码,例如:
在分支(A,B,C之前)之前,在文件Z中我们有:
Code 1
Code 2
在feature
分支(D,E)中,提交者已移除Code 2
,他还添加了Code 3
,因此文件变为:
Code 1
Code 3
在开发分支(F,G)中,添加Code 4
,因此文件变为:
Code 1
Code 2
Code 4
现在在合并(H)之后,文件变为:
Code 1
Code 3
Code 4
但合并已删除Code 2
,因为它已在feature
中删除。
我想要的是什么:
Code 1
Code 2
Code 3
Code 4
合并没有显示此文件的任何冲突。
那么如何在保留新代码的同时保留旧代码呢?
需要注意的一点是,我已将此合并推送到远程仓库。
答案 0 :(得分:2)
在我了解下面的所有详细信息之前,如果您只是询问如何轻松地(相对)找到已删除的代码,您可以在合并基础上运行git diff
和两个提交。这是git merge
合并的同一组差异。要查找合并基础,请使用git merge-base
:
$ merge=1234567... # or some other way to locate the merge commit
$ git merge-base --all ${merge}^1 ${merge}^2
理想情况下,这会打印出一个提交ID,即合并基础。然后,您可以git diff
针对${merge}^1
(合并的第一个父级)和${merge}^2
(合并的第二个父级)的哈希ID进行 A <-- branch1
/
...--*
\
B <-- branch2
。
作为Tim Biegeleisen noted in a comment,您的图表似乎与您的文字不匹配。幸运的是,从文本中我们可以描述一个更简单的情况,其中问题来自单独的文件&#39;互动。 效果是相同的,但设置更容易:
A
这里我们在branch1
上有一个提交B
,在branch2
上有一个不同的提交*
,它们都来自一个公共基本分支,其提示为{{1} }。 (对于即将到来的合并,我们只需要关心提交*
。)
假设在合并库中,prepare()
中定义的函数shared.py
实际上并未在任何地方使用。用于branch1
的意味着,现在branch1
的{{1}}使用了。但work.py
中提交B
的作者决定删除 branch2
,因为它从未被使用过。
您现在希望将提交prepare()
和A
合并,或许作为B
上的新提交:
branch1
Git将提交$ git checkout branch1
$ git merge branch2
与提交*
进行比较:除了其他内容之外,还要说明&#34;在文件A
&#34;中添加对prepare
的调用。对于Git来说这没问题,因为提交work.py
甚至没有触摸文件B
。
Git然后将提交work.py
与提交*
进行比较:除了其他事项之外,这说明&#34;从文件B
删除对prepare
的调用&#34; 。这也不是Git的问题,因为提交shared.py
不会触及A
的这一部分(甚至可能根本不触及shared.py
。)
结果是新的合并提交:
shared.py
你的工作,作为合并的人,告诉Git是否正确合并。 Git会得到这个合并错误的,部分原因是谁写了提交 A---C <-- branch1
/ /
...--* /
\ /
B <-- branch2
做错了。如果您只是运行B
并推送结果,因为合并成功,那么您的错误就在那里,您将需要从中恢复。我们马上就会谈到这一点,但首先,让我们看看如何尽早避免或纠正错误。
这并不完美,但具有通过计算机轻松完成的优势。如果有好的测试,您可以执行git merge
然后运行测试。如果测试失败,你知道合并有问题 - 所以停止使用它,然后返回并修复它。
这会捕获更多错误,但也会错过更多错误,因为它可以捕获未经过测试的问题,但依赖于错误的人类。
如果您发现在测试期间未显示的问题,您可以编写自动测试来检测它。如果这不需要太长时间,这是一个好主意。
由于合并未在任何地方使用,您可以取消它:
git merge
放弃合并(提交$ git reset --hard HEAD^
),以便C
再次指向branch1
。我们也不需要保留索引或工作树,因为Git以完全自动化的方式进行合并。我们现在必须决定如何解决问题。
A
branch2
这给了我们:
$ git checkout branch2
... edit shared.py to restore the code ...
$ git add shared.py; git commit
在 A <-- branch1
/
...--*
\
B--C <-- branch2
上创建D
的合并将再次成功,但不会删除必要的代码。我们可以重新合并并重新测试。
branch1
现在我们可以修复$ git merge --no-commit branch2
,shared.py
并提交。这个选项很快,但有点脏,因为我们现在有一个合并提交,它有一个由Git本身看到的问题而不是的手动修复。如果通过自动化测试发现问题,并且我们一直使用自动化测试,并且由于某种原因我们将来必须重复此合并,我们将重新发现问题。如果我们也在合并消息中对此进行评论,那可能就足够了。
此选项 的优势在于我们无需向git add
添加其他提交。
选项1的一个轻微变体比其中任何一个工作更多,但对于某些案例(令人不安的branch2
是一个问题)可能是最好的:使用固定版本的branch2
创建一个 new 分支,然后将其合并,以便我们得到:
B
当然,我们现在所拥有的是合并已被推送(或发布)。撤回已发布的合并比撤消已发布的常规(非合并)提交更难,因为还原合并会使我们在后续合并中遇到不同但相关的失败。所以我们可以离开&#34;坏&#34;那里的版本,只需修复并推动:
A---D <-- branch1
/ /
...--* C <-- fixed-branch2
\ /
B <-- branch2
提交 A---C--D <-- branch1
/ /
...--* /
\ /
B <-- branch2
,我们将其作为D
的新提示,只是放弃了缺失的功能 - 就像我们直接修复branch1
时一样。
这使branch2
落后于#34;破坏了提交&#34;。这基本上只是事物的方式:人们有承诺;人们正在使用它;所以我们有点坚持下去。如果/当我们使用C
来查找问题时,它会导致头痛,因为提交git bisect
并不真正起作用,但唯一的选择是可以其他人< / em>从他们的存储库中清除损坏的提交C
- 在这种情况下,您可以&#34;重写历史记录&#34;假装你首先使用前两个选项中的一个。
(幸运的是C
可以处理损坏的提交 - 如果要保留历史记录,可以使用git bisect
在二等分期间注释这些提交以实现自动化。 http://xkcd.com/974/了解详情。:-))