短版
是的。
使用--full-history,如果需要,还可以使用-m 和更多标志。
您可以使用git cherry-pick 将原始提交重新复制到新的合并后提交;或者手动做一些可能更简单。您可以在任何给定的“坏”合并上使用git show -m 来获取针对双方父母的差异,然后手动应用丢弃的更改,或者甚至按摩差异并使用git apply。或者,您可以在错误合并之前检查提交,自己手动重新进行合并,将“错误”合并的树与更正的树进行比较,从而提出一个可能适用于的补丁你现在拥有的提交链。
第 2 点的加长版
使用git log --full-history -- <path> 避免丢弃一些触及给定路径的提交。如果没有--full-history,Git 倾向于丢弃仅从一侧修改文件的合并,例如,在每个文件的基础上,使用仅我们或他们的策略解决冲突。 (这对我来说似乎是一个错误:某些提交限制的情况,就像这里的路径一样,可能应该 include 一些甚至所有组合 diff 显式丢弃的合并 em>。但这是一个哲学上的改变/“错误”,而不是一个彻底的“明显不正确的行为”错误。)
添加-m --name-status 以观察合并提交本身的修改。添加--merges 以观察only 合并:
git log --full-history -m --name-status --merges -- <path>...
您可以将--name-status 替换为-p 以使git log 显示差异。或者,一旦您发现了可疑的合并(通过其哈希 ID),请使用 git show -m <hash> 详细查看它们。
请注意,对于 -m,git log 和 git show 的合并输出会发生一些变化:
commit <sha1> (from <parentN>)
Merge: <parent1> <parent2>
Author: ...
-m 标志在这里所做的是将合并拆分为多个虚拟提交,每个虚拟提交一个。然后针对该特定父级进行差异。
场景(或者你是怎么到这里的,或者第 1 点的长版本)
很多时候,有人运行git merge 并通过简单地选择“左侧版本”或“右侧版本”而不是“大部分来自基础,并在适当的情况下从左侧一点点”来错误处理合并冲突, 并根据需要从右边一点点”。结果是冲突文件丢失了一半合并的所有更改。
有时是正确的做法,有时则不是。 Git 产生了冲突,因为至少有一些更改发生了冲突。当某些更改发生冲突时,很可能只有“A 列”或“B 列”有正确答案。但是我们可能在同一个文件的不同行中存在非冲突的更改。
例如,考虑这个(相当人为的)文本文件的片段:
# base column A column B
94: We show that We show that We show that
95: 2 + 2 = 5. 2 + 2 = 3. 2 + 2 = 4.
96:
97:
98:
99:
100:
101:
102: This moth proof... This math proof... This moth proof...
“基本”版本有两个错误,一个是 2 + 2 = 5 的声明,另一个是 moth proof。
“A 列”中一个分支上的版本试图纠正它:2+2 不是 5,它是数学(或 "maths")证明,而不是飞蛾证明。
另一个分支上的版本,“B 列”,确实纠正了它——但那个版本的作者错过了第 102 行的错误。
在这种情况下,正确的合并策略是从第二个更改的版本中获取第 95 行,从第一个更改的版本中获取第 102 行。一些合并工具使这很容易正确地完成——我自己在一个文件上使用vim 与merge.conflictstyle = diff3 合并冲突标记,因此甚至不必查看第 102 行没有冲突——一些合并工具试图向你展示一个全局视图,让你很容易看到这个并说“哦,只使用第二个父级”(B栏)。
简而言之,您的理论可能是正确的(尽管git reset 可能不是罪魁祸首)。但是,要确定这一点,您必须观察同一个人重新执行相同的合并 - 假设他或她尚未学会正确地执行合并!
第 3 点的加长版
我认为,绘制一些提交图时效果最好。您可以从各种工具中获取此信息,包括 git log --graph(可选使用 --oneline 和 --decorate 之类的标志),以及 gitk 之类的可视化工具。他们倾向于垂直呈现图表,较新的提交位于顶部。出于文本原因,我喜欢将它们水平绘制,将较新的提交向右绘制。
假设图表当前看起来像这样:
...--o--*--o--o--o--E--M--o--o--F <-- branch
\ /
A--B--C--D-´ <-- topic [label may no longer exist]
这里,* 是错误合并 M 的合并基础。当时有人破坏了东西,更主要的branch 的尖端是提交E,而topic 分支的尖端是提交D。进行合并的人在分支branch,事情看起来像这样:
...--o--*--o--o--o--E <-- branch
\
A--B--C--D <-- topic
他们运行了git merge branch,或者可能从某些 GUI 中执行了等效操作,导致他们走上了错误的合并花园路径。这产生了合并冲突,他们不恰当地解决并提交了。
您可以重新进行整个合并。只需按 ID 签出 commit E,和/或给它一个分支名称。使用git checkout -b,我们可以同时做到这两点:
git checkout -b remerge <id-of-E>
(或者,您可以在“分离 HEAD”模式下执行此操作,这就是我为快速一次性测试所做的操作。稍后您可以随时使用 git checkout -b 为分离的 HEAD 命名。)
现在提交 E 是最新的,只需重新运行合并:
git merge <id-of-D>
Git 将执行与“当时”相同的合并操作,因为它从同一个图表开始将同一个提交合并到同一个提交中。 (注意:如果您启用了git rerere,您可能希望在此处暂时禁用它,特别是如果您是错误合并的人。:-))
如果您现在解决冲突(这次正确)并提交结果,您将获得一个新的合并 M2:
...--o--*--o--o--o--E--M--o--... <-- branch
\ \/
A--B--C--D-´\
\ \
`--M2 <-- remerge
您现在可以比较 M 与 M2:
git diff <id-of-M> HEAD
查看将M 更改为M2 所需的内容。
这里有很多选择
不管你有没有M2,你都可以:
-
查看提交 A--B--C--D,如果您只需要其中的部分或全部,请使用 git cherry-pick 将它们复制到您现在所在的位置:
...--o--*--o--o--o--E--M--o--o--F--A'-B'-C'-D' <-- branch
\ /
A--B--C--D-´
这里的A'、B'、C' 和D' 是A 到D 的精选副本。
-
尝试git apply-ing 在M 和M2 之间的差异F:
...--o--*--o--o--o--E--M--o--o--F--G <-- branch
\ /
A--B--C--D-´
(将git diff 输出保存在一个文件中,或者重新运行git diff 并通过管道传输到git apply)。
您现在甚至可以将M2 合并到branch 中(尽管这可能会很混乱:提交F 和M2 的合并基础是D 和@ 的虚拟合并987654399@,这是一个糟糕的情况,因为我们首先在这里假设了冲突!),或者挑选M2 对抗它的第一个父级E。
根据情况,如果没有太多,我可能会选择樱桃采摘A--B--C--D,甚至将它们复制到新的主题分支并合并;或者只是git apply-ing M-vs-M2 diff。
使用 rebase 复制主题分支以另一种方式重做合并
“复制到新主题分支”方法可能值得在这里单独描述一下,因为git rebase 是执行此操作的命令。以下是如何使用git rebase 将topic 复制到retopic。假设我们从这个开始:
...--o--*--o--o--o--E--M--o--o--F <-- branch
\ /
A--B--C--D-´ <-- topic
首先,我们需要一个名为retopic的分支:
git checkout -b retopic topic
(如果名称 topic 消失了,使用提交的 ID D。)现在我们有了:
...--o--*--o--o--o--E--M--o--o--F <-- branch
\ /
A--B--C--D-´ <-- topic, HEAD -> retopic
现在只需运行git rebase --onto branch <id-of-E>。如果E 的 ID 不方便,但提交的 ID A 是,使用<id-of-A>^(注意帽子后缀)生成提交的 ID *。我们在这里所做的只是指示git rebase 复制以D 结尾的提交(其中retopic 点),并从提交A 开始(通过排除* 和更早的提交,这些提交可以从提交@ 访问987654426@)。
在发生冲突时解决冲突——您可能希望在开始变基之前启用git rerere——完成后,您将拥有:
A'-B'-C'-D' <-- HEAD -> retopic
/
...--o--*--o--o--o--E--M--o--o--F <-- branch
\ /
A--B--C--D-´ <-- topic
您现在可以 git checkout branch 和 git merge --no-ff retopic 从 rebase 复制的提交中进行 new 合并 M2。 (注意:A' 到 D' 的部分或全部可能在复制过程中完全丢失,具体取决于错误合并 M 中保留的内容。)