如何恢复多个Git提交?

我有一个Git存储库,看起来像这样:

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

我希望分支的头部指向A,也就是说,我希望B,C,D和HEAD消失,我希望head与A同义。

听起来我可以尝试rebase(不适用,因为我已经在其间推送了更改)或恢复。但是如何恢复多个提交?我一次恢复一个吗?顺序重要吗?

933893 次浏览
git reset --hard agit reset --mixed dgit commit

这将作为一次对所有这些文件的恢复。给出一个好的提交消息。

扩展我在评论中写的内容

一般规则是,你不应该重写(更改)你已经发布的历史,因为有人可能已经基于它进行了他们的工作。如果你重写(更改)历史,你会在合并他们的更改和更新他们时遇到问题。

因此,解决方案是创建一个新的提交,其中恢复更改您想要删除。您可以使用git恢复命令执行此操作。

您有以下情况:

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

(这里的箭头指的是指针的方向:在提交的情况下是“父”引用,在分支头(分支ref)的情况下是顶部提交,在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 for all of them"

适用于除合并提交之外的所有内容。


替代解决方案是提交A的结帐内容,并提交此状态。也适用于合并提交。但是,添加的文件不会被删除。如果您有任何本地更改,请先git stash它们:

$ git checkout -f A -- . # checkout that revision over the top of local files$ git commit -a

那么你会遇到以下情况:

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

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


替代解决方案由Jeff Ferland,由Charles Bailey修改基于相同的想法,但使用了git重置。这里稍微修改了一下,这种方式适用于一切:

$ git reset --hard A$ git reset --soft D # (or ORIG_HEAD or @{1} [previous location of HEAD]), all of which are D$ git commit

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

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

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-commit $i; done

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

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

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

例如,如果你有一个提交树,如

 o---o---o---o---o---o--->fff eee ddd ccc bbb aaa

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

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done

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

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

git revert master~3..master

git revert B...Dgit revert D C B

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

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

雅库布的回答类似,这允许您轻松选择要恢复的连续提交。

# Revert all commits from and including B to HEAD, inclusivelygit revert --no-commit B^..HEADgit commit -m 'message'

首先确保您的工作副本没有被修改。

然后:

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

然后提交。不要忘记记录恢复的原因。

如果您想临时恢复功能的提交,那么您可以使用以下一系列命令。

这是它的工作原理

git log --pretty=oneline | grep 'feature_name' | cut -d ' ' -f1 | xargs -n1 git revert --no-edit

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

git checkout <branch_name>git reset --hard <commit Hash for A>git push -f

我在阅读Jakub的帖子中学到了很多,但是公司里的某个人(可以在没有拉取请求的情况下推送到我们的“测试”分支)推送了五次错误的提交,试图修复和修复他五次提交前犯的错误。不仅如此,还有一两个拉取请求被接受,现在是坏的。所以忘了它;我找到了最后一个好的提交(abc1234),并且运行了基本脚本:

git checkout testinggit reset --hard abc1234git push -f

我告诉在这个存储库中工作的其他五个人,他们最好记下过去几个小时的更改,并从最新的测试中擦除/重新分支。故事结束了。

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

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

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

git checkout -f <target-commit> -- .

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

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

git diff --name-status --cached <target-commit>

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

git rm <filespec>[ <filespec> ...]

最后,提交reversion:

git commit -m 'revert to <target-commit>'

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

git diff <target-commit> <current-commit>

不应有任何分歧。

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

git revert HEADgit revert HEAD~2git revert HEAD~4git rebase -i HEAD~3 # pick, squash, squash

工作就像一个魅力:)

干净的方式,我发现有用

git revert --no-commit HEAD~3..git commit -m "your message regarding reverting the multiple commits"

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

也不会重写历史,所以不需要强制推送。

..帮助创建一个范围。意义HEAD~3..HEAD~3..HEAD相同

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

回到A

git checkout -f A

点主的头到当前状态

git symbolic-ref HEAD refs/heads/master

保存

git commit

我真的想避免硬重置,这就是我想出的。

A -> B -> C -> D -> HEAD

返回到A(返回4步):

git pull                  # Get latest changesgit reset --soft HEAD~4   # Set back 4 stepsgit stash                 # Stash the resetgit pull                  # Go back to headgit stash pop             # Pop the resetgit commit -m "Revert"    # Commit the changes

如果你

  1. 有一个合并的提交和
  2. 你无法恢复,并且
  3. 你不介意粉碎你要恢复的历史,

那你就可以

git reset --soft HEAD~(number of commits you'd like to revert)git commit -m "The stuff you didn't like."git log# copy the hash of your last commitgit revert <hash of your last (squashed) commit>

然后,当你想推送更改时,请记住使用-f标志,因为你修改了历史记录

git push <your fork> <your branch> -f

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

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

我发现自己需要恢复长范围的提交,然后重新恢复它们以帮助团队提出明确的拉取请求,而无需强制推动他们的目标分支(直接提交)

# checkout the branch that should be targetedgit checkout $branch_target
# revert the commits in $branch_target to some $count where#   $count is the number of commits to revert#   cut is used to slice just the commit hash field from each line of output#   xargs runs the command once for each line of input, reversing the commits!git log --oneline -n $count | cut -d' ' -f1 | xargs git revert
# check out the branch which should be the source of the pull requestgit checkout -b $branch_for_pull
# revert the revert commits# $count is that same number of commits being reverted (again)git log --oneline -n $count | cut -d' ' -f1 | xargs git revert
# push branches up and go off to create PR in whatever web UIgit push --set-upstream origin $branch_for_pull  # it's new!git checkout $branch_targetgit push  # if this branch wasn't pushed, just fix the issue locally instead..

因为这会以相反的顺序将所有提交从HEAD恢复到git log -n $count,所以它可以很好地处理任何数量的提交

$branch_target查看此状态

% git log --oneline origin/$branch_targetffff006 (origin/$branch_target, $branch_target) Revert "first commit"ffff005 Revert "second commit"ffff004 Revert "third commit"ffff003 third commitffff002 second commitffff001 first commit

$branch_for_pull查看此状态

% git log --oneline origin/$branch_for_pullffff009 (origin/$branch_for_pull, $branch_for_pull) Revert "Revert "third commit""ffff008 Revert "Revert "second commit""ffff007 Revert "Revert "first commit""ffff006 (origin/$branch_target, $branch_target) Revert "first commit"ffff005 Revert "second commit"ffff004 Revert "third commit"ffff003 third commitffff002 second commitffff001 first commit

如果意图是用变更集创建N个分支,但它们都提交到同一个分支,你仍然可以将它们全部恢复到基本提交,然后只恢复所需的恢复,因为变更集应该在逻辑上排序(尝试说5倍快)

使用像HEAD~7..HEAD~5这样的语法可能有助于描述精确分割恢复-恢复分支的范围

在这里,当恢复最后7次提交(git log -n 7)时是有意义的,但是在一个分支(git log -n 5)中恢复5,然后在另一个git log HEAD~12..HEAD~10中恢复2(12是7次提交+5次提交,假设新的PR分支是基于分支“之前”它,或者FF(非压扁)将分支“之前”合并到原始目标分支的结果)

使用GitRestore

您也可以使用restore命令:

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

假设你想让HEAD看起来和A一模一样。确保你已经拉取了最新的master。然后切一个新分支。

git switch -c feature/flux-capacitor  # synonymous with checkout -bgit restore --source A .git add .git commitgit push

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

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

文档:git恢复