将Git合并提交的基础更改为Rebasing

259

假设有如下情况:

我在一个专题分支上完成了一些工作,现在准备合并回主分支(master):

* eb3b733 3     [master] [origin/master]
| * b62cae6 2   [topic]
|/  
* 38abeae 1

我从主分支合并代码,解决了冲突,现在我的代码库里有:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | eb3b733 3                     [origin/master]
|/  
* 38abeae 1

现在,由于合并花费了我一些时间,所以我进行了另一个fetch并注意到远程主分支有新的更改:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
| | * e7affba 4                   [origin/master]
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

如果我从主分支(master)尝试运行git rebase origin/master,那么我将被迫再次解决所有的冲突,并且还会丢失合并提交(merge commit):

* d4de423 2       [master]
* e7affba 4       [origin/master]
* eb3b733 3
| * b62cae6 2     [topic]
|/  
* 38abeae 1

有没有一种简单的方法可以变基合并提交,使得最终历史记录看起来像我下面展示的那样?

*   51984c7 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | e7affba 4                     [origin/master]
* | eb3b733 3
|/  
* 38abeae 1

93
TL;DR: git rebase --preserve-merges origin/master - Ilia K.
6
关于需要重新解决冲突的问题,你可能想要查看 git rerere - Parker Coates
1
git config --global pull.rebase preserve 用于在 rebase 过程中始终保留合并提交。 - galath
13
警告:从Git 2.18版本开始(即2018年第二季度,5年后),git --rebase-merges将最终取代旧的git --preserve-merges。请参见Git的“rebase --preserve-merges”到底是做什么的(以及为什么)? - VonC
更短的命令是 git rebase -p origin/master - Felipe Alvarez
15
--preserve-merges已经被弃用,请使用 git rebase --rebase-merges origin/master。该命令的作用是保留分支合并信息。 - Arjun Sreedharan
5个回答

198

这里有两个选项。

一个是进行交互式变基并编辑合并提交,手动重新执行合并并继续变基。

另一个是在git rebase命令中使用--rebase-merges选项,手册描述如下:

默认情况下,变基将简单地从待办事项清单中删除合并提交,并将变基后的提交放入单一的线性分支中。使用 --rebase-merges 选项,变基将尝试保留要变基的提交中的分支结构,通过重新创建合并提交。 任何已解决的合并冲突或手动修订这些合并提交都必须手动解决/重新应用。


18
我尝试了 -p 选项,它确实保留了我想要的提交历史记录,但它迫使我再次解决冲突,即使是那些在 origin/master 中未被编辑过的文件也需要。关于你的第一个建议,可以告诉我确切的命令序列是什么? - jipumarino
3
@jipumarino说:使用git rebase -i命令(编辑合并提交),当它到达合并提交时,执行git reset --hard HEAD^、git merge、解决冲突、git commit、git rebase --continue。你可能还想看看git rerere,它可以帮助处理这种情况(但我从未使用过,无法提供任何建议或帮助)。 - siride
3
谢谢。我启用了 Rerere 并尝试使用 rebase -p 命令,它的效果如预期那样。 - jipumarino
4
这是一篇描述这种情况的绝佳博客文章:在 Git 中对合并提交进行变基 - kynan
1
重复并不是解决方案,因为您仍然需要手动解决第一次合并。 - Flimm
显示剩余6条评论

60

好的,这是一个旧问题,并且已经有一个被接受的答案由 @siride 给出,但是这个答案在我的情况下不够用,因为 --preserve-merges 强制你要再次解决所有冲突。我的解决方案基于 @Tobi B 的想法,但带有确切的步骤命令。

我们将从原始问题中发现的相同状态开始:

*   8101fe3 Merge branch 'topic'  [HEAD -> master]
|\  
| * b62cae6 2                     [topic]
| |
| | * f5a7ca8 5                   [origin/master]
| | * e7affba 4
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

请注意我们比主分支多了2个提交,因此无法使用cherry-picking。

  1. 首先,让我们创建正确的历史记录:

 git checkout -b correct-history # create new branch to save master for future
 git rebase --strategy=ours --preserve-merges origin/master

我们使用--preserve-merges来保存历史记录中的合并提交。 我们使用--strategy=ours来忽略所有的合并冲突,因为我们不关心合并提交中会有哪些内容,我们只需要一个良好的历史记录。

历史记录将如下所示(忽略主分支):

 *   51984c7 Merge branch 'topic'  [HEAD -> correct-history]
 |\  
 | * b62cae6 2                     [topic]
 * | f5a7ca8 5                     [origin/master]
 * | e7affba 4
 * | eb3b733 3
 |/  
 * 38abeae 1
  • 现在让我们获取正确的索引。

  •  git checkout master # return to our master branch
     git merge origin/master # merge origin/master on top of our master
    

    我们可能会遇到一些额外的合并冲突,但这只会涉及在 8101fe3f5a7ca8 之间更改的文件引起的冲突,它不包括来自 topic 已经解决的冲突。

    历史记录将如下所示(忽略正确的历史记录):

     *   94f1484 Merge branch 'origin/master'  [HEAD -> master]
     |\  
     * | f5a7ca8 5                   [origin/master]
     * | e7affba 4
     | *   8101fe3 Merge branch 'topic'
     | |\  
     | | * b62cae6 2                     [topic]
     |/ /
     * / eb3b733 3
     |/  
     * 38abeae 1
    
  • 最后一步是将我们的分支与正确的历史记录和正确的索引合并

  •  git reset --soft correct-history
     git commit --amend
    

    我们使用reset --soft来重置分支(和历史记录)为正确的历史记录,但保留索引和工作树。然后我们使用commit --amend来重新编写合并提交,该提交曾经具有不正确的索引,现在使用主分支上良好的索引。

    最终,我们将拥有这种状态(请注意顶部提交的另一个ID):

     *   13e6d03 Merge branch 'topic'  [HEAD -> master]
     |\  
     | * b62cae6 2                     [topic]
     * | f5a7ca8 5                     [origin/master]
     * | e7affba 4
     * | eb3b733 3
     |/  
     * 38abeae 1
    

    这太棒了,帮了很多忙!但我没弄懂commit --amend的技巧:你能再加些信息吗?运行它后到底会发生什么——我注意到提交的SHA已经改变了——但为什么?或者如果不运行它会发生什么? - ZenJ
    1
    @ZenJ git commit --amend 将更改添加到最后一次提交(在这种情况下是合并提交)。由于提交内容发生更改,哈希值也会更新。 - Dries Staelens
    1
    对于在解决冲突前未启用“rerere”的人来说,这个解决方案非常好,因为它可以帮助你避免再次解决冲突。谢谢! - Shackleford
    因此,总结一下:#!/bin/bash onto=$1; branchname=$(git rev-parse --abbrev-ref HEAD); git branch -D tmp-correct-history; git checkout -b tmp-correct-history; git rebase --strategy=ours --preserve-merges $onto; git checkout $branchname; git merge $onto; git reset --soft tmp-correct-history; git commit --amend - qbolec

    8
    鉴于我刚刚浪费了一天时间来解决这个问题,并且在同事的帮助下找到了解决方案,我认为我应该发表一下意见。
    我们有一个庞大的代码库,需要同时处理2个分支的大量修改。如果您愿意,其中有一个主要分支和一个次要分支。
    当我将次要分支合并到主分支中时,主分支上的工作仍在继续进行,到我完成时,我无法推送我的更改,因为它们不兼容。
    因此,我需要“变基”我的“合并”。
    以下是我们最终的做法:
    1)记下SHA。例如:c4a924d458ea0629c0d694f1b9e9576a3ecf506b
    git log -1
    

    2)创建适当的历史记录,但这将破坏合并。

    git rebase -s ours --preserve-merges origin/master
    

    3) 记下SHA值。例如:29dd8101d78

    git log -1
    

    4) 现在将其重置为之前的状态

    git reset c4a924d458ea0629c0d694f1b9e9576a3ecf506b --hard
    

    5) 现在将当前的主分支合并到你的工作分支中。

    git merge origin/master
    git mergetool
    git commit -m"correct files
    

    6) 现在你已经有了正确的文件,但是历史记录不对,请使用以下命令将正确的历史记录与你的更改合并:

    git reset 29dd8101d78 --soft
    

    7) 然后--修正您原始合并提交的结果

    git commit --amend
    

    嘿!


    1

    看起来你想要做的是删除你的第一个合并。你可以按照以下步骤进行:

    git checkout master      # Let's make sure we are on master branch
    git reset --hard master~ # Let's get back to master before the merge
    git pull                 # or git merge remote/master
    git merge topic
    

    那会给你想要的东西。


    4
    启用 rerere 后,这似乎会得到与 siride 上述提供的 rebase -p 解决方案相同的结果。 - jipumarino

    -2
    • 从您的合并提交中
    • 挑选新变更应该很容易
    • 复制您的内容
    • 重新执行合并并通过从本地副本中复制文件来解决冲突 ;)

    1
    这个答案看起来不错,但如果您提供实际的git命令,对于新手用户会更有用。 - Louise Davies

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接