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

Posted

技术标签:

【中文标题】非线性合并后如何恢复线性git历史?【英文标题】:How to restore linear git history after nonlinear merge? 【发布时间】:2019-12-11 04:35:47 【问题描述】:

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

我制作了一个虚拟 repo,它模拟了我的真实情况,目的是让这更简单。这是它的 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 中的相同图表:

这是一个 mspaint 可视化,我希望我的主人看起来像 - 它本质上应该就像我会在合并之前重新定位: (哈希值会改变)

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

【问题讨论】:

虽然这违背了一般的 git 实践,但我不会质疑你为什么想要一个 strict 线性历史,但你可以做的是挑选你的提交或你可以做this 【参考方案1】:

我觉得没那么难,只要记住它需要重写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,现在你可以强制推送分支

【讨论】:

抱歉,我想出了一个非常简单的方法来实现这一点。【参考方案2】:

一种方法是使用变基。

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

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

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

为了解决这个问题,我会做以下事情:

    查看“原始分支”,即commit nr。 9 在这里创建一个新分支,只是为了确保我们可以玩一会儿 将这个新分支(由提交 3、6、7 和 9 组成)重新设置在 master 之上,就像最初合并时一样,所以在提交 8 之上 解决任何合并冲突(您在最初合并时也遇到了这些冲突,但现在可能需要以不同的方式处理它们,因为与合并相比,rebase 的操作方式) 完成此操作后,检查 master 上的最后一个 previous 提交,即 11,然后在新分支之上重新设置提交 10 和 11 如果一切现在看起来不错,您可以将 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'(' 表示“提交的副本,相同的内容,新的哈希”)

                            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

在 TEMP1 之上重新设置这个分支(10 和 11):

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

验证 TEMP2 是否与当前 master 相同,没有丢失,没有添加等。

然后将 master 硬重置为 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 存储库,然后,如果可以,在该副本中执行以下命令,如果搞砸了,请删除该副本然后重新开始,不要在没有安全网的情况下跳跃

    git checkout <HASH-OF-9> git checkout -b TEMP1(是的,您可以使用git checkout -b TEMP1 <HASH-OF-9> 在一个命令中执行此操作和上一个命令) git rebase -i --onto <HASH-OF-8> <HASH-OF-2> TEMP1 解决合并冲突并提交(如果有) git checkout -b TEMP2 <HASH-OF-11>git rebase --onto TEMP1 <HASH-OF-MERGE> TEMP2 检查是否一切正常 git checkout mastergit reset --hard TEMP2

最后,清理:

git branch -d TEMP1 TEMP2
git push -f

只有在您知道一切正常时才强制推送

【讨论】:

【参考方案3】:

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

【讨论】:

我想我不明白它到底是如何工作的,但是这个解决方案很好,而且证明真的很有帮助! 这似乎是最简单的解决方案,

以上是关于非线性合并后如何恢复线性git历史?的主要内容,如果未能解决你的问题,请参考以下文章

git如何恢复本地删除的文件

如何从 GIT 恢复旧的拉取请求

恢复错误合并后恢复丢失的更改

个人作坊,vs 集成 GIT版本控制,GIT恢复历史版本时,总提示有冲突要求合并. 请问怎么才能还原到历史版本

从git文件删除后如何恢复文件?

我如何恢复 `git fetch 上游; git 合并上游/master`? [复制]