如何在非线性合并后恢复 Git 的线性历史记录?

24

我之前提交代码的时候,在主分支上意外地做了一次非线性合并。我经常习惯于保持一个线性的历史记录,因此现在我想恢复这个线性性。

我已经创建了一个虚拟仓库,模拟了我真正面临的情况,以便更简单地说明。以下是其在 GitHub 上的链接:https://github.com/ruohola/merge-question

下面是运行 git log --oneline --graph --date-order 命令的结果:

* 88a4b7e (HEAD -> master, origin/master, origin/HEAD) 11
* 5aae63c 10
*   5506f33 Merge branch 'other'
|\
| * b9c56c9 9
* | 3c72a2a 8
| * 8d2c1ea 7
| * 35f124b 6
* | 7ca5bc1 5
* | b9e9776 4
| * fd83f02 3
|/
* 4fa8b2e 2
* cbdcf50 1

在Sourcetree中的同一张图表:

git log in sourcetree

这里有一个Mspaint可视化图,它展示了我想要让我的主分支看起来像什么——实际上就像我在合并前进行了变基
(散列值会改变)

wanted end result

我知道这可能不是最佳实践,我也熟悉重写历史的后果(不过没有其他人在这个分支上工作),但我仍然想要能够做到这一点。如何实现?


2
尽管这与一般的git实践相违背,但我不会质疑你为什么想要一个严格的线性历史。你可以挑选你的提交或者做这个 - mnestorov
3个回答

15

我认为这并不难,只需要记住它需要重写主分支的历史记录:

git checkout b9c56c9
git rebase 3c72a2a # rebase on top of the other branch
git cherry-pick 5506f33..master # reapply changes from merge revision (dropping it) up until the tip of master
# if you like the results
git branch -f master
git checkout master

如果您已经在另一个远程上有了旧版本的主分支,现在您可以强制推送该分支。


1
抱歉,我不得不取消接受你的答案,因为我想出了一种非常简单的方法来实现这个。 - ruohola

11

或许最简单的方法是“滥用”git rebase的默认行为。也就是说,不需要显式地传递--rebase-merges参数给git rebase,它会从历史记录中移除所有合并提交。这使我们能够非常轻松地得到所需的结果:

之前:

~/merge-question (master) $ git log --oneline --graph --date-order
* 88a4b7e (HEAD -> master, origin/master, origin/HEAD) 11
* 5aae63c 10
*   5506f33 Merge branch 'other'
|\
| * b9c56c9 9
* | 3c72a2a 8
| * 8d2c1ea 7
| * 35f124b 6
* | 7ca5bc1 5
* | b9e9776 4
| * fd83f02 3
|/
* 4fa8b2e 2
* cbdcf50 1

运行命令:

~/merge-question (master) $ git rebase 3c72a2a
First, rewinding head to replay your work on top of it...
Applying: 3
Applying: 6
Applying: 7
Applying: 9
Applying: 10
Applying: 11

之后:

~/merge-question (master) $ git log --oneline --graph --date-order
* d72160d (HEAD -> master) 11
* 90a4718 10
* 3c773db 9
* ba00ecf 7
* 9e48199 6
* 24376c7 3
* 3c72a2a 8
* 7ca5bc1 5
* b9e9776 4
* 4fa8b2e 2
* cbdcf50 1

完成后,只需简单地执行git push --force-with-lease origin master命令,远程版本库的历史记录便会回到线性状态。


1
我觉得我不完全理解这是如何工作的,但解决方案很好,确实非常有帮助! - Nre
1
这似乎是最简单的解决方案。 - sola

10

一种方法是使用 rebase。

无论选择哪种方法,你都必须重写你的仓库历史记录。你必须接受这个事实,否则就得接受当前的历史记录。

让我们总结一下你的历史记录的不同部分:

  • 提交4、5和8,在主分支上
  • 提交3、6、7和9现在也在主分支上,但最初位于不同的分支上
  • 提交10和11在主分支上,上面合并了两个并行的历史记录

为了解决这个问题,我会执行以下步骤:

  1. 检出“原始分支”,也就是第 9 个提交
  2. 在此处创建一个新的分支,只是为了确保我们可以玩耍一下
  3. 将这个由提交3、6、7和9组成的新分支(当你最初合并时)重新定位到主分支的顶部,因此定位到提交8的顶部
  4. 解决任何合并冲突(当你最初合并时,你也会遇到这些问题,但由于 rebase 操作与合并操作的不同,可能需要以不同的方式处理它们)
  5. 完成以上步骤后,请检出主分支上的上一个提交,即 11 号提交,并将提交 10 和 11 重新定位到你的新分支上
  6. 如果一切现在看起来都很好,你可以硬重置主分支到这个新分支,并强制推送到你的远程仓库以使它成为新的历史记录

以下是该过程的逐步图示(命令如下):

现在的状态:

                         master
                            v
1---2---4---5---8---M--10--11
     \             /
      3---6---7---9

为9创建新分支:

                         master
                            v
1---2---4---5---8---M--10--11
     \             /
      3---6---7---9
                  ^
                TEMP1

将基于8进行变基,这将创建3'、6'、7'、9'(其中'表示“提交的副本,内容相同,新的哈希值”)

                            TEMP1
                              v
                  3'--6'--7'--9'
                 /
1---2---4---5---8---M--10--11
     \             /        ^
      3---6---7---9      master
创建一个新分支用于11(我不喜欢动主分支)。
                            TEMP1
                              v
                  3'--6'--7'--9'
                 /
1---2---4---5---8---M--10--11
     \             /        ^
      3---6---7---9      master
                            ^
                          TEMP2

将这个分支(10和11)在TEMP1的顶部进行变基:

                            TEMP1   TEMP2
                              v       v
                  3'--6'--7'--9'-10'-11'
                 /
1---2---4---5---8---M--10--11
     \             /        ^
      3---6---7---9      master

确认TEMP2与当前主分支完全相同,没有丢失也没有添加任何内容。

然后将主分支硬重置为TEMP2:

                                    master
                                      v
                            TEMP1   TEMP2
                              v       v
                  3'--6'--7'--9'-10'-11'
                 /
1---2---4---5---8---M--10--11
     \             /
      3---6---7---9

我将删除TEMP1和TEMP2这两个分支。

请注意,提交3、6、7、9、M、10和11仍然存在于存储库中,但它们不直接可用,因为没有任何东西引用它们。因此,它们有资格进行垃圾回收,实际上您的存储库的实际历史现在是这样的:

1---2---4---5---8---3'--6'--7'--9'-10'-11'
                                        ^
                                     master

执行这些操作的命令是:

(第0步:完全复制您的本地文件夹,包括工作文件夹和.git存储库。如果可以,请在该副本中执行以下命令。如果出错,请删除副本并重新开始,不要没有安全网就跳)

  1. git checkout <HASH-OF-9>
  2. git checkout -b TEMP1 (是的,您可以使用 git checkout -b TEMP1 <HASH-OF-9> 一条命令同时执行前面两个命令)
  3. git rebase -i --onto <HASH-OF-8> <HASH-OF-2> TEMP1
  4. 解决合并冲突并提交更改(如果有的话)
  5. git checkout -b TEMP2 <HASH-OF-11>
    git rebase --onto TEMP1 <HASH-OF-MERGE> TEMP2
  6. 检查是否一切正常
  7. git checkout master
    git reset --hard TEMP2

最后,清理:

git branch -d TEMP1 TEMP2
git push -f

只有在你确定一切都没问题的情况下才能使用 force-push


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