撤销 Git Rebase

27

我在自己的分支上执行了git rebase master,但直到推送到远程后才意识到这不是我想要的。目前只有我和另一个人在项目上工作,所以我知道他们还没有拉取最新的更改。

从StackOverflow上阅读其他问题,它说要使用git reflog,然后使用git reset --hard HEAD@{n}回到rebase之前的状态。我确实使用了这个命令回到了一个在rebase之前创建的提交,但它并没有像以前一样恢复所有内容。

我错过了什么步骤吗?是否有一种方式让另一个人强制将他的仓库回退到以前的状态?

谢谢


2
“它没有将事情恢复到之前的状态。” - Sam
在变基之前,我有几个额外的提交和提交消息没有显示出来,图表显示了2层而不是只有1层。 - user1960364
让我们回到之前的地方:git rebase master基本上是将你本地主分支中的内容移动到了你本地主分支中的最新位置。你的本地主分支是否与远程分支不同步?解决这个问题并不是一个简单的过程。 - Makoto
1
我在主分支上,进行了一次拉取操作,做了一个快速更改并将其推送到主分支。我想让这个更改出现在branch2上,所以我切换到了branch2并运行了git rebase master命令。我只是想让最后一次提交也出现在branch2上,但它却做了其他的事情(我对Git还不熟悉,所以不确定如何解释它做了什么)。但是有几个提交消失了或被合并在一起了,不确定是哪个。我想将其恢复到所有这些操作之前的状态。 - user1960364
1
可能是撤销git rebase的重复问题 - phuclv
3个回答

61

正如Makoto已经指出的那样,您可能不需要费心撤销此次变基:这可能是您想要的。尽管如此,如果您想要撤销它,请继续阅读以下内容。


使用reflog来查看分支信息,因为它更易于阅读。(HEAD reflog中也有相同的信息,但是包含了大量其他内容,因此很难找到你要查找的内容。)
例如,如果我刚刚对mybranch进行了rebase,我将会看到:
$ git reflog mybranch
nnnnnnn mybranch@{0}: rebase finished: refs/heads/mybranch onto biguglysha1
ooooooo mybranch@{1}: commit: some sort of commit message
...

这里的名字mybranch@{1}目前等同于旧的简称SHA-1码ooooooo。每次对该分支进行操作(例如git reset),@{...}部分的数字会改变,而SHA-1码则永久不变,因此使用SHA-1码(完整或简称)进行剪切和粘贴更加安全。

接下来:

$ git checkout mybranch # if needed

并且:

$ git reset --hard ooooooo  # or mybranch@{1}

您应该拥有原始版本。这是因为rebase仅仅是复制提交并移动标签。在rebase之后,但在reset之前,提交图看起来像这样,其中AC是"您"的提交:
          A - B - C              <-- (only in reflog now)
        /
... - o - o - o - A' - B' - C'   <-- mybranch (after rebase)

git reset 只是简单地擦除当前分支标签,并将其粘贴到提供的 SHA-1 上(如果需要,首先将 reflog 名称转换为 SHA-1)。 因此,在执行 reset 后:

          A - B - C              <-- mybranch, plus older reflog
        /
... - o - o - o - A' - B' - C'   <-- (only in reflog now)

请注意,现在在reset之后,rebase所做的提交副本是“废弃”的,只能在reflog条目中找到。被废弃的原始提交现在又归属于mybranch了。
思考这些东西的方法是画出提交图(新提交指向其父提交),然后用长箭头标记分支标签。图形从不改变,除了添加新提交,这些提交具有新的和不同的SHA-1哈希值(这就是为什么我使用字母如ABC,并附加像A'这样的副本)。SHA-1哈希保证是唯一且永久的,但带有长箭头的标签却经常被擦除和重新指向。(如果你在白板上做这个,你应该通常用黑色表示提交图和颜色或几种颜色表示标签。)

1嗯,git reset并不仅仅是移动标签,除非您添加一些命令行标志。默认情况下,它移动标签并且重置索引;使用--hard,它移动标签并且重置索引并且清空您的工作树。使用--soft,它只是移动标签,保留索引和工作树。由于git是什么样的工具,还有许多其他标志可以进一步扭曲其含义,但这些是最重要的三个:--soft,无标志即--mixed--hard

2如果git只添加而不删除,你的仓库会随着时间变得非常庞大。因此,当“不可达”提交(即没有标签,也没有任何剩余的reflog条目指向它们,并且没有被某个具有标签或其他指向的提交所指向的提交)存在时,git会在自动运行git gc时删除这些不可达的提交(以及任何其他不可达的对象)。你可以强制提前删除它们,但很少有充分的理由去打扰它们。

3Git本身依赖于这种保证。任何两个不同的对象出现相同SHA-1的可能性是数学上可能的,但极其不可能。如果发生这种情况,git就会出问题。4如果分布足够好,则概率为2160中的1,这非常微小。这是一件好事,因为"生日悖论"会很快提高可能性,但由于开始非常微小,因此保持微小,在实践中从未成为问题。

按照设计,“breakage”的意思是git停止添加对象,这样到目前为止的一切仍然是好的。然后你需要转移到处理数十亿个对象存储库的新系统,即“下一代”git或其他系统。

什么是从另一个分支拉取更改的正确方式,就像我需要在这里一样,但不会丢失我的分支现有的提交历史记录?我应该使用交互式变基、合并还是其他方法?谢谢。 - user1960364
我不太明白你所说的“我的分支现有提交历史”的含义:一方面是你的git在分支reflog中保留的历史记录,另一方面是由提交图所暗示的历史记录。在我回答的例子中,提交图的历史记录是“C指向B,B指向A,A指向另一个未被区分的提交o”。还有另一种历史记录,“C'指向B',B'指向A',A'指向另一个未被区分的提交”。当然,这两者是不同的 - 但是,如果没有人关心他们使用哪个未被区分的提交o,那么它们都可以接受。 - torek

4
我有好消息和坏消息要告诉你。
好消息是,你现在处于你所期望的状态!master上的最新提交已经合并到你的分支中,宇宙万物都很美好。
坏消息是,通过rebase操作,你实际上将你的分支移动到了master的最新状态;也就是说,你的开发分支现在与你直接从master创建分支的状态相同。
但这是一件好事:只要你没有遇到任何合并冲突,在这个过程中你没有丢失任何数据,也不会在你的分支上留下无用的合并提交。 这本书很好地解释了什么是rebase,所以我不再赘述。根据你描述的操作,你不应该丢失任何工作。
如果您希望在强制推送之前重新创建原始历史记录,则拥有未拉取历史记录的人可以将其分支强制推送到远程存储库。这将导致您的存储库状态为他们推送时的状态。

但是我想要我的提交历史记录,难道没有办法恢复吗?正如我在原帖中所述,我正在与的人还没有拉取 - 所以如果我无法修复它,他是否可以使用他的本地存储库恢复到上一个状态?将来,我应该如何处理这个问题,以便不会丢失我的提交历史记录? - user1960364
当然可以。但我希望你明白两件事情:你已经“绝对”重写了历史,这应该只在非常谨慎的情况下进行;而你所描述的情况并不是一个坏情况。你已经在你的分支上得到了你想要的提交,并且你有了一个更干净、更线性的历史记录。但是,如果你对此感到不舒服,你的合作伙伴可以强制推送他们的分支。 - Makoto

-1

如果您从reflog(也是普通Git日志中条目的尾部)中获取提交哈希,则可以执行get reset --hard COMMIT,然后git push -f origin master,这通常是一种不好的操作。在执行强制推送之前,我建议将完整的历史记录检出到单独的干净目录中。


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