1331

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

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

我想让分支的头指向A,即我想让B、C、D和HEAD消失,我想让head成为A的同义词。

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

4

16 回答 16

1757

扩展我在评论中写的内容

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

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

您有以下情况:

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

(这里的箭头指的是指针的方向:提交情况下的“父”引用,分支头(branch 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 reset。在这里稍作修改,这种方式适用于一切:

$ git reset --hard A
$ git reset --soft D # (or ORIG_HEAD or @{1} [previous location of HEAD]), all of which are D
$ git commit
于 2009-09-24T08:44:40.757 回答
497

我发现有用的清洁方式

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

此命令仅使用一次提交还原最后 3 次提交。

也不会重写历史,因此不需要强制推送。

..有助于创建范围。意思HEAD~3..是一样的HEAD~3..HEAD

于 2017-03-29T00:00:00.783 回答
269

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

考虑到您的示例,您必须这样做(假设您在分支“master”上):

git revert master~3..master

git revert B...Dgit revert D C B

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

A <- B <- C <- D <- BCD' <- HEAD
于 2012-07-31T15:00:45.823 回答
90
git reset --hard a
git reset --mixed d
git commit

这将立即对所有人进行恢复。给出一个好的提交信息。

于 2009-09-23T00:45:31.410 回答
84

与 Jakub 的回答类似,这使您可以轻松选择要恢复的连续提交。

# revert all commits from and including B to HEAD, inclusively
$ git revert --no-commit B^..HEAD  
$ git commit -m 'message'
于 2014-07-31T18:49:15.610 回答
65

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

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

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

于 2014-09-09T13:13:27.470 回答
56

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

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

我从 Jakub 的帖子中学到了很多东西,但是公司里的一些人(可以在没有 Pull-Request 的情况下推送到我们的“测试”分支)推送了 5 次错误提交,试图修复并修复他在 5 次提交前犯的错误。不仅如此,还有一两个 Pull Requests 被接受了,现在已经很糟糕了。所以算了吧,我找到了最后一个好的提交(abc1234)并运行了基本脚本:

git checkout testing
git reset --hard abc1234
git push -f

我告诉在这个 repo 中工作的其他 5 个人,他们最好记下他们在过去几个小时内所做的更改,并从最新的测试中擦除/重新分支。故事的结局。

于 2016-07-11T23:29:24.287 回答
17

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

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

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

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

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

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

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

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

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

最后,提交还原:

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

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

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

应该没有区别。

于 2016-11-08T06:39:47.590 回答
9

还原共享存储库(人们使用并且您希望保留历史记录)上的一组提交的简单方法是git revert与 git 结合使用rev-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
于 2011-08-04T11:29:23.127 回答
7

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

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

像魅力一样工作:)

于 2017-01-24T00:28:34.760 回答
6

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

回到A

git checkout -f A

将主人的头指向当前状态

git symbolic-ref HEAD refs/heads/master

节省

git commit
于 2018-11-16T17:18:02.070 回答
3

可能不如这里的其他方法优雅,但我一直习惯于get reset --hard HEAD~N撤消多个提交,N你想要返回的提交数量在哪里。

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

于 2021-01-20T18:02:38.100 回答
2

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

# checkout the branch that should be targeted
git 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 request
git 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 UI
git push --set-upstream origin $branch_for_pull  # it's new!
git checkout $branch_target
git push  # if this branch wasn't pushed, just fix the issue locally instead..

因为这会将所有提交从HEADgit log -n $count以相反的顺序恢复,所以它可以很好地和干净地处理任意数量的提交

$branch_target这个状态查看

% git log --oneline origin/$branch_target
ffff006 (origin/$branch_target, $branch_target) Revert "first commit"
ffff005 Revert "second commit"
ffff004 Revert "third commit"
ffff003 third commit
ffff002 second commit
ffff001 first commit

$branch_for_pull这个状态查看

% git log --oneline origin/$branch_for_pull
ffff009 (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 commit
ffff002 second commit
ffff001 first commit

如果打算使用变更集创建 N 个分支,但它们都提交到同一个分支,您仍然可以将它们全部还原回基本提交,然后只还原所需的还原,因为变更集应按逻辑排序(尝试说快 5 倍)

使用类似的语法HEAD~7..HEAD~5可能有助于描述范围以精确拆分还原-还原分支

git log -n 7在这里,还原最后 7 次git log -n 5提交git log HEAD~12..HEAD~10(关闭“之前”的分支,或者 FF(非压缩)的结果将“之前”的分支合并到原始目标分支中)

于 2021-08-03T16:26:06.673 回答
1

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

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

回到 A(向后退了 4 步):

git pull                  # Get latest changes
git reset --soft HEAD~4   # Set back 4 steps
git stash                 # Stash the reset
git pull                  # Go back to head
git stash pop             # Pop the reset 
git commit -m "Revert"    # Commit the changes
于 2020-06-30T10:17:09.137 回答
0

如果你

  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 commit
git revert <hash of your last (squashed) commit>

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

git push <your fork> <your branch> -f
于 2021-01-16T05:56:29.653 回答
-8

如果您想暂时还原某个功能的提交,则可以使用以下一系列命令。

下面是它的工作原理

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

于 2016-02-11T18:47:34.390 回答