如何恢复多个git提交?

Bil*_*ill 877 git commit git-revert

我有一个git存储库,如下所示:

A -> B -> C -> D -> HEAD
Run Code Online (Sandbox Code Playgroud)

我希望分支的头部指向A,即我希望B,C,D和HEAD消失,我希望头部与A同义.

听起来我可以尝试改变(不适用,因为我推动了之间的变化),或者还原.但是如何恢复多个提交?我一次还原一个吗?订单重要吗?

Jak*_*ski 1229

扩展我在评论中写的内容

一般规则是您不应该重写(更改)您已发布的历史记录,因为有人可能已将其工作基于它.如果您重写(更改)历史记录,则会在合并更改和更新时遇到问题.

所以解决方案是创建一个新的提交,它可以恢复你想要删除的更改.您可以使用git revert命令执行此操作.

您有以下情况:

A <-- B  <-- C <-- D                                               <-- master <-- HEAD

(这里的箭头指的是指针的方向:提交时的"父"引用,分支头(分支引用)的顶部提交,以及HEAD引用的分支名称).

您需要创建的内容如下:

A <-- B  <-- C <-- D <-- [(BCD)^-1]                   <-- master <-- HEAD

其中"[(BCD)^ - 1]"表示恢复提交B,C,D中的更改的提交.数学告诉我们(BCD)^ - 1 = D ^ -1 C ^ -1 B ^ -1,所以您可以使用以下命令获取所需的情况:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"
Run Code Online (Sandbox Code Playgroud)

替代解决方案是检查 提交A的内容,并提交此状态:

$ git checkout -f A -- .
$ git commit -a
Run Code Online (Sandbox Code Playgroud)

那么你会遇到以下情况:

A <-- B  <-- C <-- D <-- A'                       <-- master <-- HEAD

提交A'与提交A具有相同的内容,但是是不同的提交(提交消息,父项,提交日期).

由Charles Bailey修改的Jeff Ferland解决方案建立在同样的想法上,但使用git reset:

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit
Run Code Online (Sandbox Code Playgroud)

  • 除了很好的答案.这个简写适用于我`git revert --no-commit DCB` (76认同)
  • 如果您在B,C或D中添加了文件,`git checkout -f A - .`将不会删除这些文件,您必须手动执行.我现在应用了这个策略,感谢Jakub (38认同)
  • 这些解决方案并不等同.第一个不删除新创建的文件. (17认同)
  • @Jerry:`git checkout foo`可能意味着checkout*branch*`foo`(切换到分支)或checkout*file*foo(来自索引).`--`用于消除歧义,例如`git checkout - foo`总是关于文件. (9认同)
  • @ welldan97:感谢您的评论.写这个答案时,`git revert`不接受多次提交; 这是一个很新的补充. (8认同)
  • FWIW,如果您需要删除这些新文件,`git clean`就是您的朋友. (2认同)
  • 嗨,你能解释一下所有的旗帜和开关吗?git checkout -f A - . - >' - '和'.'是什么 意思? (2认同)
  • @JakubNarębski “可能”是什么意思?我也觉得这个命令很神秘...... (2认同)
  • 这个答案很好地解释了所有选项,但是我们不应该将git reset --soft @ {1}解决方案放在顶部吗?显然,这是解决问题的最佳方法。 (2认同)
  • 为了澄清`git checkout -f A -- .`,我认为这意味着“在不进入分离HEAD模式的情况下检查提交A到当前目录的更改”,基本上,您留在master中,但带来提交A的更改及时到达当前地点。至少我是这么理解的,如有错误,请指正。 (2认同)
  • @Flatline:`git checkout -f A - .`表示将提交A的**状态**签出到当前目录,覆盖文件......但它不会删除文件以使其进入"A"状态. (2认同)
  • 我去了 git checkout -f A -- 。因为我有 8 次提交并且喜欢 1 步的想法。这大部分工作,但是我会注意到新文件没有被删除,只是更改了文件撤消,我在运行此命令并推送后将远程头​​与我的樱桃选择分支进行了比较,并且只看到新文件,删除这些文件后,然后提交/推送,分支是相同的。 (2认同)

小智 214

为此,您只需使用revert命令,指定要恢复的提交范围.

考虑到你的例子,你必须这样做(假设你在分支'master'):

git revert master~3..master
Run Code Online (Sandbox Code Playgroud)

这将在您的本地创建一个新的提交,使用B,C和D的反向提交(意味着它将撤消这些提交引入的更改):

A <- B <- C <- D <- BCD' <- HEAD
Run Code Online (Sandbox Code Playgroud)

  • `git revert --no-commit HEAD~2..`是一种稍微习惯的方法.如果您在主分支上,则无需再次指定master.`--no-commit`选项允许git尝试立即恢复所有提交,而不是用多个`revert commit ...`消息乱丢历史记录(假设这是你想要的). (115认同)
  • @Victor我修复了你的提交范围.范围的开头是独家的,这意味着它不包括在内.因此,如果你想恢复最后3次提交,你需要从第3次提交的父***开始范围,即`master~3`. (6认同)
  • @kubi没有办法在提交消息中包含SHA,使用单个提交(您的方法,但无需手动输入已还原的提交)? (2认同)

dee*_*ive 190

干净的方式,我发现有用

git revert --no-commit HEAD~3..
Run Code Online (Sandbox Code Playgroud)

此命令仅使用一次提交恢复最后3次提交.

也不会重写历史记录.

  • 这是简单而最好的答案 (12认同)
  • 如果某些提交是合并提交,这将不起作用. (9认同)
  • 最后的两个点是做什么的? (5认同)
  • @cardamom指定范围.`HEAD~3..`与`HEAD~3..HEAD`相同 (5认同)
  • @JohnLittle它负责更改。从那里的`git commit`实际上会执行提交。 (2认同)

kon*_*yak 64

与Jakub的答案类似,这使您可以轻松选择要还原的连续提交.

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'
Run Code Online (Sandbox Code Playgroud)

  • 您的解决方案对我来说很好,但稍作修改.如果我们有这种情况Z - > A - > B - > C - > D - > HEAD并且如果我想要返回A状态,那么奇怪的是我将不得不执行git revert --no-commit Z ..头 (8认同)
  • 范围是错误的.它应该是'B ^ .. HEAD`,否则B被排除在外. (8认同)
  • 同意@Bogdan,还原范围如下:SHA_TO_REVERT_TO..HEAD (2认同)
  • 同意@tessus,所以正确的做法是:`git revert --no-commit B ^ .. HEAD`或`git revert --no-commit A..​​HEAD` (2认同)

Jef*_*and 55

git reset --hard a
git reset --mixed d
git commit
Run Code Online (Sandbox Code Playgroud)

这将立刻成为所有人的回复.给出一个好的提交消息.

  • 这可能以某种方式完成git reset --keep吗? (5认同)
  • 是的,我认为git reset --keep正是我上面所说的.它出现在2010年4月发布的1.7.1版本中,所以答案当时并不存在. (5认同)
  • 如果他希望`HEAD`看起来像'A`那么他可能希望索引匹配,所以`git reset --soft D`可能更合适. (4认同)
  • --soft resetting不会移动索引,因此当他提交时,看起来直接来自而不是来自D的提交.这会使分支分裂.--mixed保留更改,但移动索引指针,因此D将成为父提交. (2认同)
  • 为什么需要`git reset --mixed D`?具体为什么是`reset`?是不是因为在没有重置为 D 的情况下,HEAD 会指向 A,导致 B、C 和 D 被“悬空”并被垃圾收集——这不是他想要的?但是为什么是`--mixed`?您已经回答了“`--soft` 重置不会移动索引...”所以通过移动索引,这意味着索引将包含 D 的更改,而工作目录将包含 A 的更改——这样一个 `git status`或 `git diff`(将索引 [D] 与工作目录 [A] 进行比较)将显示内容;那个用户要从 D 回到 A? (2认同)

mat*_*lka 36

首先要确保您的工作副本未被修改.然后:

git diff HEAD commit_sha_you_want_to_revert_to | git apply
Run Code Online (Sandbox Code Playgroud)

然后只是提交.不要忘记记录恢复的原因.

  • 这是比接受的答案更灵活的解决方案。谢谢! (2认同)
  • 回复:二进制文件使用--binary选项:`git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply` (2认同)
  • 即使您想要还原包含合并提交的一系列提交,这也将起作用。当使用`git revert A..​​Z`时,您会得到`error:commit X是merge但没有给出-m选项。 (2认同)
  • 哇,太酷了,正是我正在寻找的。所以它是反向差异,然后将这些更改应用于现有代码。非常聪明,谢谢。;) (2认同)

meh*_*meh 36

使用 GitRestore

您还可以使用restore命令:

A <- B <- C <- D <- HEAD
Run Code Online (Sandbox Code Playgroud)

假设您想HEAD看起来一模一样A。确保您已拉取最新的main. 然后剪出一个新的树枝。

git switch -c feature/flux-capacitor  # synonymous with checkout -b
git restore --source A .
git add .
git commit
git push
Run Code Online (Sandbox Code Playgroud)

restore命令将所有内容 ( .) 更改为提交时的内容--source。然后,您将其提交到本地分支并将其推送到原点。然后你可以针对它打开一个 PR。

这样做的好处是不会改变其他人可能基于工作的任何历史。也为后人留下了有用的历史。

文档:git 恢复

  • 这对我来说非常有用,因为我不想使用重置来删除提交。运行这些命令后,我当然只是切换到 master 并合并。谢谢。 (2认同)

Sua*_*ere 28

我很沮丧,这个问题不能回答.所有其他问题都与如何正确还原和保存历史有关.这个问题说"我希望分支的负责人指向A,即我希望B,C,D和HEAD 消失,我希望头部与A同义".

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f
Run Code Online (Sandbox Code Playgroud)

我学到了很多阅读Jakub的帖子,但该公司的一些人(可以通过推送到我们的"测试"分支而没有Pull-Request)推动了5个糟糕的提交,试图修复并修复并修复他5次提交之前的错误.不仅如此,还接受了一两个Pull请求,现在已经很糟糕了.所以忘掉它,我找到了最后一个好的提交(abc1234)并且只运行了基本脚本:

git checkout testing
git reset --hard abc1234
git push -f
Run Code Online (Sandbox Code Playgroud)

我告诉在这个回购中工作的其他5个人,他们最好记下他们在过去几个小时内的变化以及最新测试中的Wipe/Re-Branch.故事的结局.

  • 我没有发布提交,所以这是我需要的答案。谢谢,@Suamere。 (4认同)
  • 更好的方法是`git push --force-with-lease`,只有在没有其他人提交到分支之后或在蒸发的范围内时,它才会重写历史记录。如果其他人使用了这个分支,那么它的历史永远不应该被重写,提交应该只是明显地恢复。 (4认同)
  • @Suamere 谢谢!我同意这个问题明确指出它想重写历史。我和你的情况一样,我猜OP是因为有人意外地做了几十次丑陋的恢复和奇怪的提交以及恢复的恢复(当我在度假时),并且状态需要恢复。无论如何,连同良好的健康警告一起,这应该是公认的答案。 (4认同)
  • @frandroid“历史不应该被重写”,只有绝对的西斯交易。这个线程的问题,以及我的回答的重点,正是对于一个特定的场景,所有的历史都应该被抹去。 (2认同)
  • *这*就是我需要去的地方——因为历史需要随着变化而告别。干净、正确、果断、简洁。并做了。 (2认同)

War*_*Dew 9

这是Jakub答案中提供的解决方案之一的扩展

我面临的情况是,我需要回滚的提交有点复杂,其中几个提交是合并提交,我需要避免重写历史记录.我无法使用一系列git revert命令,因为我最终遇到了添加的返回更改之间的冲突.我最终使用了以下步骤.

首先,检查目标提交的内容,同时将HEAD留在分支的顶端:

$ git checkout -f <target-commit> -- .
Run Code Online (Sandbox Code Playgroud)

( - 确保<target-commit>被解释为提交而不是文件;.指的是当前目录.)

然后,确定在回滚的提交中添加了哪些文件,因此需要删除:

$ git diff --name-status --cached <target-commit>
Run Code Online (Sandbox Code Playgroud)

添加的文件应该在行的开头显示"A",并且不应存在其他差异.现在,如果需要删除任何文件,请暂存这些文件以进行删除:

$ git rm <filespec>[ <filespec> ...]
Run Code Online (Sandbox Code Playgroud)

最后,提交reversion:

$ git commit -m 'revert to <target-commit>'
Run Code Online (Sandbox Code Playgroud)

如果需要,请确保我们恢复到所需的状态:

$git diff <target-commit> <current-commit>
Run Code Online (Sandbox Code Playgroud)

应该没有差异.

  • 这对我来说是一个更好的解决方案,因为我在我的合并提交. (2认同)

Rus*_*lin 7

在共享存储库(人们使用并且您希望保留历史记录)上恢复一组提交的简单方法是git revert与 git 结合使用rev-list。后者将为您提供提交列表,前者将自行执行还原。

有两种方法可以做到这一点。如果您希望在一次提交中恢复多个提交,请使用:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-commit $i; done
Run Code Online (Sandbox Code Playgroud)

这将恢复您需要的一组提交,但将所有更改保留在您的工作树上,之后您应该像往常一样提交所有更改。

另一种选择是对每个还原的更改进行一次提交:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done
Run Code Online (Sandbox Code Playgroud)

例如,如果你有一个像这样的提交树

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa
Run Code Online (Sandbox Code Playgroud)

要将更改从eee恢复为bbb,请运行

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
Run Code Online (Sandbox Code Playgroud)


nul*_*lll 7

在我看来,一个非常简单和干净的方法可能是:

回到A

git checkout -f A
Run Code Online (Sandbox Code Playgroud)

将master的头指向当前状态

git symbolic-ref HEAD refs/heads/master
Run Code Online (Sandbox Code Playgroud)

节省

git commit
Run Code Online (Sandbox Code Playgroud)

  • 您能解释一下投反对票的原因吗? (2认同)
  • 这很好用。对这个有用的答案投反对票有什么意义,或者有人可以解释什么是最佳实践吗? (2认同)

Dor*_*ian 6

这些都不适合我,所以我有三个提交要恢复(最后三个提交),所以我做了:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash
Run Code Online (Sandbox Code Playgroud)

像魅力一样工作:)

  • 仅当您的更改尚未推送时,这是一个可行的选项。 (3认同)

Ian*_*Ian 5

可能不如这里的其他方法优雅,但我总是用来get reset --hard HEAD~N撤消多个提交,其中N是您想要返回的提交数量。

或者,如果不确定提交的确切数量,只需git reset --hard HEAD^多次运行(返回一次提交),直到达到所需状态。