如何恢复多个 Git 提交?

发布于 2024-08-05 09:57:57 字数 223 浏览 12 评论 0原文

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

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

我希望分支的头指向 A,即我希望 B、C、D 和 HEAD 消失,并且我希望 head 与 A 同义。

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

I have a Git repository that looks like this:

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

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.

It sounds like I can either try to rebase (doesn't apply, since I've pushed changes in between), or revert. But how do I revert multiple commits? Do I revert one at a time? Is the order important?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

走野 2024-08-12 09:57:57

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

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

因此,解决方案是创建一个新提交,它恢复您想要删除的更改。您可以使用 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

Expanding what I wrote in a comment

The general rule is that you should not rewrite (change) history that you have published, because somebody might have based their work on it. If you rewrite (change) history, you would make problems with merging their changes and with updating for them.

So the solution is to create a new commit which reverts changes that you want to get rid of. You can do this using git revert command.

You have the following situation:

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

(arrows here refers to the direction of the pointer: the "parent" reference in the case of commits, the top commit in the case of branch head (branch ref), and the name of branch in the case of HEAD reference).

What you need to create is the following:

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

where [(BCD)^-1] means the commit that reverts changes in commits B, C, D. Mathematics tells us that (BCD)-1 = D-1 C-1 B-1, so you can get the required situation using the following commands:

$ 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"

Works for everything except merge commits.


Alternate solution would be to checkout contents of commit A, and commit this state. Also works with merge commits. Added files will not be deleted, however. If you have any local changes git stash them first:

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

Then you would have the following situation:

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

The commit A' has the same contents as commit A, but is a different commit (commit message, parents, commit date).


Alternate solution by Jeff Ferland, modified by Charles Bailey builds upon the same idea, but uses git reset. Here it is slightly modified, this way WORKS FOR EVERYTHING:

$ git reset --hard A
$ git reset --soft D # (or ORIG_HEAD or @{1} [previous location of HEAD]), all of which are D
$ git commit
柳絮泡泡 2024-08-12 09:57:57

我发现有用的干净方式

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

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

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

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

Clean way which I found useful

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

This command reverts last 3 commits with only one commit.

Also doesn't rewrite history, so doesn't require a force push.

The .. helps create a range. Meaning HEAD~3.. is the same as HEAD~3..HEAD

橘和柠 2024-08-12 09:57:57

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

考虑到您的示例,您必须执行此操作(假设您位于分支“master”上):

git revert master~3..master

git revert B...Dgit revert DC B code>

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

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

For doing so you just have to use the revert command, specifying the range of commits you want to get reverted.

Taking into account your example, you'd have to do this (assuming you're on branch 'master'):

git revert master~3..master

or git revert B...D or git revert D C B

This will create a new commit in your local with the inverse commit of B, C and D (meaning that it will undo changes introduced by these commits):

A <- B <- C <- D <- BCD' <- HEAD
寄与心 2024-08-12 09:57:57
git reset --hard a
git reset --mixed d
git commit

这将立即恢复所有这些内容。给出一个好的提交消息。

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

That will act as a revert for all of them at once. Give a good commit message.

波浪屿的海角声 2024-08-12 09:57:57

类似于 Jakub 的答案,这可以让您轻松选择要恢复的连续提交。

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

Similar to Jakub's answer, this allows you to easily select consecutive commits to revert.

# Revert all commits from and including B to HEAD, inclusively
git revert --no-commit B^..HEAD
git commit -m 'message'
潜移默化 2024-08-12 09:57:57

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

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

阅读 Jakub 的帖子,但是公司里的某个人(可以在没有 Pull-Request 的情况下推送到我们的“测试”分支)推送了五个错误的提交,试图修复、修复和修复他五次提交前犯的错误。不仅如此,还接受了一两个拉取请求,但现在这些请求很糟糕。所以算了吧;我找到了最后一个好的提交(abc1234),然后运行了基本脚本:

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

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

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 a Pull-Request) pushed like five bad commits, trying to fix and fix and fix a mistake he made five 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 five guys working in this repository that they better make note of their changes for the last few hours and wipe/rebranch from the latest testing. End of the story.

只是在用心讲痛 2024-08-12 09:57:57

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

然后:

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

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

First be sure that your working copy is not modified.

Then:

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

And then just commit. Don't forget to document what the reason is for the revert.

无悔心 2024-08-12 09:57:57

使用 Git Restore

您还可以使用 restore 命令:

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

假设您希望 HEAD 看起来与 A 完全相同。确保您已提取最新的 main。然后剪出一个新的树枝。

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

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

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

文档:git 恢复

Using Git Restore

You can also use the restore command:

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

Let's say you want HEAD to look exactly like A. Make sure you've pulled the latest main. Then cut a new branch.

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

The restore command changes everything (.) to what it was at the --source commit. Then you commit that to your local branch and push it to the origin. You can then open a PR against it.

This has the benefit of not changing any history that others might have based work on. It also leaves a useful history for folks in the future.

Docs: git restore

烟沫凡尘 2024-08-12 09:57:57

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

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

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

git checkout -f <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>

不应该有任何差异。

This is an expansion of one of the solutions provided in Jakub's answer.

I was faced with a situation where the commits I needed to roll back were somewhat complex, with several of the commits being merge commits, and I needed to avoid rewriting history. I was not able to use a series of git revert commands because I eventually ran into conflicts between the reversion changes being added. I ended up using the following steps.

First, check out the contents of the target commit while leaving HEAD at the tip of the branch:

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

(The -- makes sure <target-commit> is interpreted as a commit rather than a file; the . refers to the current directory.)

Then, determine what files were added in the commits being rolled back, and thus need to be deleted:

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

Files that were added should show up with an "A" at the beginning of the line, and there should be no other differences. Now, if any files need to be removed, stage these files for removal:

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

Finally, commit the reversion:

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

If desired, make sure that we're back to the desired state:

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

There should be no differences.

海拔太高太耀眼 2024-08-12 09:57:57

恢复共享存储库(人们使用的并且您想要保留历史记录)上的一组提交的简单方法是将 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

The easy way to revert a group of commits on shared repository (that people use and you want to preserve the history) is to use git revert in conjunction with git rev-list. The latter one will provide you with a list of commits, the former will do the revert itself.

There are two ways to do that. If you want the revert multiple commits in a single commit use:

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

this will revert a group of commits you need, but leave all the changes on your working tree, you should commit them all as usual afterward.

Another option is to have a single commit per reverted change:

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

For instance, if you have a commit tree like

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

to revert the changes from eee to bbb, run

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
冷情妓 2024-08-12 09:57:57

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

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

工作得像一个魅力:)

None of those worked for me, so I had three commits to revert (the last three commits), so I did:

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

Worked like a charm :)

私野 2024-08-12 09:57:57

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

回到A

git checkout -f A

点大师的头到当前状态

git symbolic-ref HEAD refs/heads/master

保存

git commit

In my opinion a very easy and clean way could be:

go back to A

git checkout -f A

point master's head to the current state

git symbolic-ref HEAD refs/heads/master

save

git commit
伴我老 2024-08-12 09:57:57

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

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

Probably less elegant than other approaches here, but I've always used get reset --hard HEAD~N to undo multiple commits, where N is the number of commits you want to go back.

Or, if unsure of the exact number of commits, just running git reset --hard HEAD^ (which goes back one commit) multiple times until you reach the desired state.

浮光之海 2024-08-12 09:57:57

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

# 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 的语法可能有助于描述范围以精确分割revert-revert 分支

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

I found myself needing to revert a long range of commits and then re-revert them to help a team present a clear pull request without having to force-push over their target branch (which was committed directly to)

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

Because this reverts all of the commits from HEAD to git log -n $count in reverse order, it'll work well and cleanly with any number of commits

View from $branch_target at this state

% 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

View from $branch_for_pull at this state

% 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

If the intention was to create N branches with changesets, but they were all committed to the same branch, you can still revert all of them back to the base commit, then only revert the reverts needed as the changesets should be ordered logically (try saying that 5x fast)

Using a syntax like HEAD~7..HEAD~5 may help with describing the ranges to precisely split the revert-revert branches

Here, it would make sense when reverting the last 7 commits (git log -n 7), but restoring 5 with in one branch (git log -n 5) and 2 then the top-most 2 in another git log HEAD~12..HEAD~10 (12 is 7 commits + 5 commits, assuming the new PR branch is based off either the branch "before" it, or the result of a FF (non-squashed) merge the branch "before" it into the original target branch)

空袭的梦i 2024-08-12 09:57:57

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

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

I really wanted to avoid hard resets, this is what I came up with.

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

To go back to A (which is 4 steps back):

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
亚希 2024-08-12 09:57:57

如果您

  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 flag 因为您修改了历史记录

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

If you

  1. have a merged commit and
  2. you are not able to revert, and
  3. you don't mind squashing the history you are to revert,

then you can

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>

Then when you want to push your changes remember to use the -f flag because you modified the history

git push <your fork> <your branch> -f
堇色安年 2024-08-12 09:57:57

我设法用最简单的解决方案解决了这个问题

git revert D
git revert C
git revert B
git revert A

注意:DCBA不是合并提交。

I managed to solve this with the simplest solution

git revert D
git revert C
git revert B
git revert A

Note: D, C, B, A were not merge commits.

世俗缘 2024-08-12 09:57:57

我们可以通过其他方式克隆分支

,然后运行以下命令。

git reset --hard commitID
git push -f branchname

Other way we can clone the branch

and then run below command.

git reset --hard commitID
git push -f branchname
辞取 2024-08-12 09:57:57

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

这是它的工作原理

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

If you want to temporarily revert the commits of a feature, then you can use the series of following commands.

Here is how it works

git log --pretty=oneline | grep 'feature_name' | cut -d ' ' -f1 | xargs -n1 git revert --no-edit
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文