如何撤销倒数第二次提交但不撤销最后一次提交?

34
假设我有四个提交记录 A-->B-->C-->D,但是提交 C 是错误的。我该如何撤销提交 C,而不会丢失提交 D 的更改?我了解 git-revert撤消提交 的基本功能,但我无法找出如何(如果有可能)在不必重新执行其后的更改的情况下撤销特定的提交(不是最新的提交)。

2
这就是 git revert 的作用:它会反向应用(即撤销)某个提交。找到有问题的提交 C,运行 git revert <定位提交 C 的内容>,Git 会将一个新的提交应用到 HEAD 上,以撤销在 C 中所做的任何操作。(这与 Mercurial 正好相反,其中 hg revert 意味着“恢复某些文件”,而您需要使用 hg backout 来撤消特定的提交。同样的撤销动词,完全不同的含义。) - torek
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - amflare
1
没错,在Git中是这样的。如果你在Git和Hg之间来回切换,很容易搞砸(他从经验中说道 :-))。 (顺便说一句,我认为Hg的“backout”动词在这里更好;“revert”动词的问题正是你所指出的:它是“revert foo”还是“revert TO foo”?) - torek
@tbirrell 请注意,C仍将出现在您的提交历史记录中,以及新的还原提交。 您的历史记录将不会像这样:A->B->D,而是A->B->C->D->E - Ray
顺便说一句,在git中恢复到指定的提交点的方法是“重置(reset)”。 - Dan Lowe
2个回答

30

还原

git revert 命令旨在实现这一点。

git revert <hash for C>

这将创建一个新的提交,以撤销C中的更改。

重写历史

您还可以重写历史记录。通常不建议这样做,只有在您确实需要从历史记录中删除提交(例如,如果其中包含密码等内容)时才应使用。

git rebase -i <hash for B>
在编辑器中,只需删除带有C的哈希行。如果您已经推送,则适用通常的注意事项。
非交互式:
从技术上讲,所有这些选项都涉及某种合并解决方案,这意味着它们不能真正是非交互式的。唯一的区别在于所得到的历史记录不同。使用git revert,您将获得以下历史记录:
A -> B -> C -> D -> E
                    ^
                    +--- reverts C

使用 git rebase,你最终得到的历史记录如下:

A -> B -----------> E

当然,你可以直接执行 git rebase --onto B C,而不是使用 git rebase -i,但这仍然是一个交互式过程,因为你必须手动解决无法被合并策略自动解决的任何合并冲突。


同时也应该注意不要使用rebase -i来重新应用已经被公开并被他人拉取的提交。 - jbu
如果tiberrell已经推送了他的更改,那么我认为Ray的解决方案更好,因为它不会破坏历史记录。如果更改仅在本地,则rebase也可以在较少的命令中工作(尽管更容易出现用户错误)。 - jbu
1
@jbu:实际上,那是不正确的 - Ray的解决方案确实会破坏历史记录。它产生与“rebase -i”相同的结果,只是采用了迂回的方式。保留历史记录的方法是使用“git revert”。 - Dietrich Epp
你说得对,我没有仔细阅读他的命令 @DietrichEpp +1 - jbu
在Windows Git Bash上,我运行了git rebase -i HEAD~2,然后在Notepad++中打开了一个预填充的提交消息,我对该怎么做感到困惑。最终我意识到我可以剪切和粘贴前两行来重新排序提交。当我关闭Notepad++时,rebase完成了(因为没有冲突),我的提交被重新排序了。然后我运行了git reset --soft HEAD~1来取消最近的提交(将它们移回到暂存区)。 - Ryan
显示剩余2条评论

3

假设:

  • A、B、C和D是提交哈希(无论何时遇到它们,都请将它们替换为哈希)
  • 您实际上希望永久从主分支中删除提交C的历史记录,而不是被“还原”提交掩盖。如果不是这种情况,请使用 git revert C

一般步骤

  • 从提交B上创建一个新分支(此分支应类似于A->B)
  • 将提交D cherry pick到新分支。(现在应该是 A->B->D'),它将获得一个新的哈希,我称之为D'
  • 切换到主分支
  • 从主分支中删除C和D提交(主要看起来像 A->B )-将你的新分支合并到主分支中(主分支现在应该是 A->B->D'
  • 删除您的测试分支。

以下是代码:

git checkout -b temp B
git cherry-pick D   
git checkout master
git reset --hard HEAD^^
git merge temp
git branch -D temp

这个过程看起来像是手动版本的 git rebase -i <B> - Dietrich Epp
1
@DietrichEpp 我在做变基时会感到紧张 :) - Ray
这样,您可以获得完整的分支来查看它。我想您可以首先在临时分支上进行变基并进行验证。 - Ray
无论如何,您仍然在执行rebase操作,但是您需要手动完成6个步骤(也许您忘记了其中一个步骤?),而不是使用1个步骤自动完成。 - Dietrich Epp
我理解你的观点。只有一个原因是在更改主分支之前能够看到最终结果,但是您可以首先在测试分支上执行rebase,验证它是否符合您的要求,然后在主分支上执行。 - Ray

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