如何恢复多个git提交?

时间:2009-09-23 00:27:14

标签: git commit git-revert

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

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

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

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

16 个答案:

答案 0 :(得分:1112)

扩展我在评论中所写的内容

一般规则是,您不应该重写(更改)您已发布的历史记录,因为有人可能会根据它开展工作。如果您重写(更改)历史记录,则会出现合并其更改以及更新这些更改的问题。

因此,解决方案是创建一个新提交,其中还原更改您想要摆脱它。您可以使用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"

备用解决方案是提交A的checkout 内容,并提交此状态:

$ git checkout -f A -- .
$ git commit -a

然后你会遇到以下情况:

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

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

solution by Jeff Ferland, modified by Charles Bailey基于相同的想法,但使用git reset

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

答案 1 :(得分:190)

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

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

git revert master~3..master

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

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

答案 2 :(得分:136)

我发现有用的清洁方式

git revert --no-commit HEAD~3..

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

也不会重写历史记录。

答案 3 :(得分:58)

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

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'

答案 4 :(得分:49)

git reset --hard a
git reset --mixed d
git commit

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

答案 5 :(得分:32)

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

git diff HEAD commit_sha_you_want_to_revert_to | git apply

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

答案 6 :(得分:25)

I'm so frustrated that this question can't just be answered. Every other question is in relation to how to revert correctly and preserve history. This question says "I want the head of the branch to point to A, i.e. I want B, C, D, and HEAD to disappear and I want head to be synonymous with A."

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

I learned a lot reading Jakub's post, but some guy in the company (with access to push to our "testing" branch without Pull-Request) pushed like 5 bad commits trying to fix and fix and fix a mistake he made 5 commits ago. Not only that, but one or two Pull Requests were accepted, which were now bad. So forget it, I found the last good commit (abc1234) and just ran the basic script:

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

I told the other 5 guys working in this repo that they better make note of their changes for the last few hours and Wipe/Re-Branch from the latest testing. End of the story.

答案 7 :(得分:7)

这是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>

应该没有差异。

答案 8 :(得分:3)

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

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

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $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

答案 9 :(得分:1)

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

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

答案 10 :(得分:0)

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

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

答案 11 :(得分: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

答案 12 :(得分:0)

我发现自己需要还原大范围的提交,然后再还原它们以帮助团队提出明确的拉取请求,而不必强行推送他们的目标分支(直接提交到)

# 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..

因为这会以相反的顺序将所有提交从 HEAD 恢复到 git 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 之类的语法可能有助于描述范围以精确拆分恢复-恢复分支

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

答案 13 :(得分:-1)

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

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

像魅力一样工作:)

答案 14 :(得分:-2)

我认为一种非常简单,干净的方法可能是:

回到A

git checkout -f A

将主人的头部指向当前状态

git symbolic-ref HEAD refs/heads/master

保存

git commit

答案 15 :(得分:-4)

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

Here is how it works

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