通过合并修改历史中的提交而不展平它

Amending commit in history with merges without flattening it

假设我有类似的历史。
Develop定期合并到master中。

a---b---c---d---i---j---k---o  master
 \             /           /
  e---f---g---h---l---m---n    develop

如何在不完全拉平历史的情况下修改 b?
如果我以交互方式变基并修改 b,我将得到如下所示的历史记录:

(大写表示提交已更改)

a---B---C---E---F---G---H---J---K---L---M---N  master
 \
  e---f---g---h---l---m---n                    develop

如何保留历史结构,并使其看起来像这样:

a---B---C---D---I---J---K---O  master
 \             /           /
  e---f---g---h---l---m---n    develop

从技术上讲,您无法更改任何现有提交。

这意味着从 "change" bB,你实际上必须制作一个新的和改进的 B。旧的 b 将继续存在(多长时间,完全是另一个问题)。常规 git rebase,交互式或非交互式,通过将提交复制到新的和改进的提交来工作。如您所述,常规变基还 删除 合并和扁平化。

从 Git 2.18 开始,git rebase 有一个名为 --rebase-merges 的模式。这保留了合并——或者更准确地说,重新创建它们作为新的合并提交,字面意思是再次 运行ning git merge。在 2.18 之前,git rebase-p,它使用交互式 rebase 机制并以其他方式保留合并(通过重复它们)。与更漂亮的新版本相比,它有些缺陷,但我认为(尚未测试!)应该适用于这种情况。

因此,请像使用 git rebase -i 一样使用 git rebase -i --rebase-merges。如果您缺少 --rebase-merges,请更新您的 Git 版本,或使用 git rebase -i -p 并非常小心不要打乱各种操作的顺序,或手动构建您的新链。

要手动构建它,运行 git checkout 提交 B。如果你愿意,你可以在这里分配一个新的分支名称,或者只是用一个分离的 HEAD 来完成整个操作,就像 git rebase 那样:

git checkout -b temp-rebuild <hash-of-b>

例如,使用新的临时分支名称。然后使用 git commit --amend(可能首先使用其他东西)来制作新的 B:

  B   <-- temp-rebuild
 /
a---b---c---d---i---j---k---o   <-- master
 \             /           /
  e---f---g---h---l---m---n   <-- develop

现在 运行 git cherry-pick 在提交 c 的散列上将其复制到新提交 C,并重复 d:

  B---C---D   <-- temp-rebuild
 /
a---b---c---d---i---j---k---o   <-- master
 \             /           /
  e---f---g---h---l---m---n   <-- develop

现在 运行 git merge 在提交 hisecond parent 的散列上,创建新的合并 I,如果需要解决任何合并冲突:

  B---C---D------I   <-- temp-rebuild
 /              /
a---b---c---d--/i---j---k---o   <-- master
 \            |/           /
  e---f---g---h---l---m---n   <-- develop

使用更多的 cherry-pick 和 merge 命令来完成这个过程:

  B---C---D------I---J---K--O   <-- temp-rebuild
 /              /          /
a---b---c---d--/i---j---k-|-o   <-- master
 \            |/          |/
  e---f---g---h---l---m---n   <-- develop

现在新的提交已经构建,强制名称 master 指向最后的提交,停止绘制旧的 b-c-d-i-j-k-o 链,你就得到了你想要的。这就是 git rebase --rebase-mergesgit rebase -p 所做的:-p 只是使用了一种脆弱的算法,而 --rebase-merges 使用了一种新的交互式指令 sheet 格式,它可以让你指定新图表在您移动提交时不会中断。