如何使用变基而不是合并来重放历史以清理拉取请求?

How to replay history using rebase instead of merge in order to clean up pull request?

一位同事是 git 的新同事。他已经在某个功能分支上工作了很长一段时间,现在创建了一个 PR(如果重要的话,是 Azure DevOps)。

不幸的是,他不知道 git rebase,所以他一直使用 git pull origin master 来同步,这导致了相当多的合并提交。 (他的功能分支上有 20 "real" 次提交和 9 次合并提交)

现在,这会很好,但结果是 PR 的差异视图不仅显示了他的更改,还显示了同时应用于 master 的所有其他更改。现在基本上不可能进行适当的代码审查,因为他的更改与所有其他更改没有区别。

现在,我的计划是以某种方式重播他的所有更改,但每当他进行合并时,我都会改为进行变基。每当在 rebase 期间发生冲突时,混合相应合并提交的更改应该可以完美解决(理论上)。大多数合并都非常困难,因此无法手动重做它们,因为这项工作已经完成。

实现该目标的最佳方法是什么?

  1. 我试过 git rebase master,但这不包括合并提交,因此必须手动完成。

  2. 我也试过git rebase --preserve-merges master,但出于某种原因,这只会在最新的合并提交之前进行变基(见下文)。

如果没有更好的选择,我将不得不做20+9 cherry-picks...


用图片说话,情况是这样的:

A---B---C- ... --D---E---F         master
 \   \      ...   \         
  G---m---H- ... --m---I---J       feature

我想要这个:

A---B---C- ... --D---E---F                                   master
                          \                          
                           G'--m'--H' ... --m'--I'--J'       feature

选项 1. 只会给我这个,但我想获得 ms(不一定作为提交,但我希望以某种方式解决那些冲突):

A---B---C- ... --D---E---F                                   master
                          \                          
                           G'------H' ... ------I'--J'       feature

选项 2. 只会给我这个,帮不上什么忙:

A---B---C- ... --D---E---F                 master
 \   \      ...   \       \         
  G---m---H- ... --m-------m'--I'--J'      feature

Rebase 无法做到这一点。它要么完全放弃合并,要么 重新执行 合并。如果它重新执行合并,则您获得的合并提交是实际的合并提交。在您的情况下,您需要相当于 -m 的樱桃采摘。 (我想你想要 -m 1,但要确定,尤其是在使用下面的内容之前。)

If there is no better option, I will have to do 20+9 cherry-picks...

这就是要走的路。使用 git rev-list 或类似的方式列出所有要复制的提交。记下哪些是合并。然后写个小脚本-let:

git cherry-pick <hash-of-G>
git cherry-pick -m 1 <hash-of-merge#1>    # or maybe -m 2?
git cherry-pick <hash-of-H>

等等。 运行 每个命令一次一个,这样您就可以知道当一个命令失败时您在哪里,或者如果您对处理错误有足够的把握,运行 它作为一个脚本 -e设置以便 cherry-pick 失败停止其余的采摘。

我找到了一个运行非常顺利的解决方案:

  1. 让自己完全处于他开始研究功能时所处的状态

    git checkout -b replay_feature <hash-of-A>  
    
  2. 重复他所做的直到他进行合并:

    git cherry-pick <hash-of-G>       # redo his non-merge commit(s)
    
  3. 现在,他做了 git merge master,所以我改为 git rebase master

    git rebase <hash-of-B>            # back then, B was HEAD of master
    
  4. 现在我正处于变基过程中,面临着他所面临的完全相同的冲突,他用 m 解决了这些冲突。因此,为了完全按照他的方式解决它,我只是从他的合并提交中检查所有冲突文件。

    git checkout <hash-of-merge> -- file-in-conflict.cpp    # do this for all conflicts 
    # make sure everything compiles
    git add .
    git rebase --continue                                  
    
  5. 重复第 4 步,直到变基完成

重复第 2 步到第 5 步,直到重播他的整个工作,用变基替换所有合并并应用他的冲突解决方案。最后,比较分支replay_featurefeature。如果一切顺利,所有文件应该是相同的。

git diff replay_feature feature    # should not show anything