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

How to restore linear git history after nonlinear merge?

很少提交之前我不小心在我的主分支中做了一个非线性合并。我有一个习惯,总是试图保持线性历史,所以现在我想恢复线性。

我制作了一个虚拟存储库,它模拟了我所拥有的真实情况,目的是使这更简单。这是一个 GitHub link: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 中的相同图表:

这里是一个 mspaint 可视化效果,展示了我想让我的 master 看起来像什么——它本质上应该就像我在合并之前重新定位一样:
(哈希值会改变)

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

我认为这并不难,请记住它需要重写 master 的历史:

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

如果您已经在另一个远程

中拥有 old master,现在您可以强制推送分支

一种方法是使用变基。

无论您选择哪种方法,您都将 必须重写存储库的历史记录。你必须接受这一点,否则你将不得不接受你当前的历史。

让我们总结一下您历史的不同部分:

  • 提交 4、5 和 8,这些在 master 上
  • 提交 3、6、7 和 9,这些现在也在 master 上,但最初在不同的分支上
  • 合并上面的两个平行历史后,提交 10 和 11 在 master 上

为了解决这个问题,我将执行以下操作:

  1. 查看"original branch",即commit nr。 9
  2. 在这里创建一个新分支,只是为了确保我们可以玩一下
  3. 将这个新分支(由提交 3、6、7 和 9 组成)重新设置在 master 之上,就像您最初合并时一样,因此在提交 8 之上
  4. 解决任何合并冲突(最初合并时也有这些冲突,但由于 rebase 与合并相比的操作方式,现在可能需要以不同方式处理它们)
  5. 完成此操作后,检查 master 上的最后一个 previous 提交,即 11,并在新分支上重新设置提交 10 和 11
  6. 如果一切现在看起来不错,您可以将 master 硬重置到这个新分支并强制推送到您的远程以使其成为新的历史记录

这是过程图,一步一步(命令如下):

现在状态:

                         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'(' 表示 "copy of commit, same contents, new hash")

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

为11创建一个新分支(我不想惹master)

                            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

只有当你知道一切正常时才强制推送

也许最简单的方法就是“滥用”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,遥控器的历史记录就会恢复为线性。