撤销 Git 中的更改(不重写历史记录)

51

我修改了一个脚本并提交了更改。然后我又做了几个其他的更改,并将它们推送到远程仓库等等。

然后我意识到我之前提到的第一个更改是愚蠢的,想要撤销它。我能否在不手动复制/粘贴差异的情况下"取消应用"该提交?

例如, 我有两个文件, a.pyb.py:

Commit 1:
I delete a function in a.py

Commit 2:
I change a few lines in b.py

Commit 3:
I change the docstring in a.py

我能否撤销这个函数的删除,并使其显示为“提交 4”(而不是删除提交 1)


14
嗨,应该写成“realised”,而不是“realized”!(我不是美国人..) - dbr
5
你的拼写被一个使用码和英尺来测量物品的人更正了……哈哈 - ddoor
4个回答

69

是的,你可以使用 git revert 来实现这个功能。请查看git手册中关于此的部分获取更多信息。

核心思想是你可以输入以下命令:

git revert 4f4k2a

4f4k2a是您想要撤消的提交的ID,它将尝试撤消它。


顺便提一下!它将删除提交1个对象。 - aatifh
1
我认为您无法从历史记录中删除提交,但可以撤消更改。 - Jeremy French
我想Git如此先进,以至于用户会忘记像还原这样简单的操作...哈哈 :-P - chakrit

36

仅仅是一个注释:

git revert aCommit

revert命令会撤销整个提交(比如“提交的所有文件”):
它会计算一个反向补丁,将其应用于HEAD并提交。

所以这里有两个问题(第一个问题很容易解决):

  • 它总是提交,因此您可能希望添加-no-commit选项:“git revert --no-commit aCommit”:当连续还原多个提交的影响到您的索引时,这非常有用。
  • 它不适用于特定文件(如果您的a.py是与其他1000个更改一起提交的,您可能不想还原),
    为了解决这个问题,如果您想提取特定文件以便它们与另一个提交相同,您应该查看git-checkout,特别是git checkout <commit> <filename>语法(尽管在这种情况下不完全是您需要的)

Easy Git(Elijah Newren)试图向Git邮件列表带来更完整的“还原”功能,但并没有取得太大成功:

有时人们想要“还原更改”。

现在,这可能是:

  • 32和29个版本之间的更改,
  • 自上次提交以来的所有更改,
  • 自3个提交以来的更改,或
  • 只是一个特定的提交。
  • 用户可能希望将此类还原子集仅限于特定文件

eg revert此处记录,但我不确定它是否是当前发行版的eg的一部分)

但最终归结为“还原更改”。

eg revert --since HEAD~3  # Undo all changes since HEAD~3
eg revert --in HEAD~8     # much like git revert HEAD~8, but nocommit by default
eg revert --since HEAD foo.py  # Undo changes to foo.py since last commit
eg revert foo.py               # Same as above
eg revert --in trial~7 bar.c baz.  # Undo changes made in trial~7 to bar.[ch]

这些“还原数据”的种类真的有如此不同吗,需要有不同的命令支持,或者某些操作不应该由简单的还原命令来支持吗?当然,大多数用户大部分时间可能会使用"eg revert FILE1 FILE2..."形式,但我认为支持额外的功能没有坏处。此外...有什么根本性的东西会阻止核心git采用这种行为吗? 注意:默认情况下提交对于广义的revert命令没有意义,"git revert REVISION"将出错并显示说明(告诉用户添加--in标志)。

假设你有50个提交,其中有20个文件,你意识到旧的提交X引入了不应该发生的更改。
需要进行一些管道工作。
你需要一种列出所有特定文件的方法,这些文件需要还原
(即“取消在提交X中所做的更改,同时保留所有后续更改”),
然后针对每个文件:

git-merge-file -p a.py X X^

这里的问题是在不删除你想要保留的 a.py 中的所有后续更改的情况下恢复丢失的函数。
这种技术有时被称为“负合并”。

由于git merge-file <current-file> <base-file> <other-file> 的意思是
将从<base-file><other-file>导致的所有更改合并到<current-file>中,因此您可以通过说您想要合并所有更改来恢复已删除的函数。)

  • 从:X(函数已被删除的位置)
  • 到:X^(函数仍然存在的上一个提交 X 的前一个提交)

注意:'-p'参数允许您先查看当前文件的更改而不对其进行任何操作。当您确定后,请移除该选项。

注意git merge-file并不简单:您不能像那样引用文件的以前版本。
(您会一遍又一遍地看到令人沮丧的消息:error: Could not stat X
您必须:

git cat-file blob a.py > tmp/ori # current file before any modification
git cat-file blob HEAD~2:a.py > tmp/X # file with the function deleted
git cat-file blob HEAD~3:a.py > tmp/F # file with the function which was still there

git merge-file a.py tmp/X tmp/F # basically a RCS-style merge
                                 # note the inversed commit order: X as based, then F
                                 # that is why is is a "negative merge"
diff -u a.py tmp/ori # eyeball the merge result
git add a.py 
git commit -m "function restored" # and any other changes made from X are preserved!

如果需要对先前提交的大量文件执行此操作...则需要编写一些脚本;)

我认为在 git checkout <commit> <filename> 中不需要破折号。 - Paul

4

VonC所指出的那样,要将提交的更改还原到一个文件中,我会checkout分支(主分支或主干或其他),然后checkout我想要还原的文件版本,并将其视为新提交:

$ git checkout trunk
$ git checkout 4f4k2a^ a.py
$ git add a.py
$ git diff              #verify I'm only changing what I want; edit as needed
$ git commit -m 'recover function deleted from a.py in 4f4k2a'

可能有一些直接执行此操作的管道命令,但如果我知道的话,我不会使用它。这并不是因为我不信任 Git,而是因为我不信任自己——我不相信我能够不看就知道在那个提交中更改了哪个文件以及此后的更改情况。而且一旦我查看了,通过编辑 diff 构建一个新提交更容易。也许这只是个人工作风格。


1
两个评论:1/如果在a.py的4f4k2a提交中删除了该函数,那么你不应该从之前的提交中检出a.py吗?(即在4f4k2a之前的提交,如4f4k2a^?)2/这样做会完全擦除当前版本的a.py吗?如果我想保留后续更改怎么办? - VonC
我宁愿使用git-merge-file进行“负合并”:请查看我的完整答案。 - VonC
  1. 是的。感谢您修复了另一个错别字。
  2. 是的 - 这就是为什么我必须看到差异。我实际上会使用一个被黑客攻击过的 diffmerge 在3路合并模式下,这样可以显示出我习惯于使用的更有用的差异,但 git diff 也是可用的。关键是:只有在查看了代码后,我才会有信心。
- Paul
是的,我认为“负向合并”会起作用。 但对我来说,当我知道我可以阅读代码并快速进行更正提交时,这需要太多时间进行研究。 我认为我在另外两个SO答案中使用了git-merge-file,在没有其他方法可行的情况下使用它,但它并不是我的日常使用。 - Paul
刚刚在我的回答中添加了git merge-file的用例(请参见结尾)...该命令不易使用:您不能仅引用文件的先前版本,而是必须将其合并到临时文件中。这令人失望... - VonC
虽然比我预想的要复杂,但现在你已经详细阐述了它,我明白了为什么这样做是必要的。非常棒的一个解决方案。我建议您删除EasyGit部分。这是很好的背景信息,但恐怕会分散你回答问题的重点。 - Paul

1

请看一下这个git revert问题。 如果不包括最近的提交,回滚旧的提交可能会出现问题。


点赞因为这很有用,但它真的应该是一条评论而不是一个答案。 - tripleee

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