ChatGPT解决这个技术问题 Extra ChatGPT

如何恢复多个 Git 提交?

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

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

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

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

如果你只是想重置遥控器,你可以用任何东西来破坏它!但是让我们使用之前的第四次提交:git push -f HEAD~4:master(假设远程分支是 master)。是的,您可以像这样推送任何提交。
如果有人拉了,您必须使用 git revert 进行提交以恢复更改。
使用 git show HEAD~4 确保您将右侧推到遥控器
“顺序重要吗?”是的,如果提交影响相同文件中的相同行。然后,您应该开始恢复最近的提交,然后按原路返回。

r
rogerdpack

扩展我在评论中写的内容

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

所以解决方案是创建一个新的提交,它还原更改,你想摆脱。您可以使用 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 的 checkout contents,并提交此状态。也适用于合并提交。但是,添加的文件不会被删除。如果您有任何本地更改,请先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 具有相同的内容,但是是不同的提交(提交消息、父母、提交日期)。

替代 solution by Jeff Ferland, modified by 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

如果您在 B、C 或 D 中添加文件。git checkout -f A -- . 不会删除这些文件,您必须手动执行。我现在应用了这个策略,谢谢 Jakub
这些解决方案并不等同。第一个不会删除新创建的文件。
@Jerry:git checkout foo 可能意味着 checkout branch foo (切换到分支)或 checkout file foo (从索引)。 -- 用于消除歧义,例如 git checkout -- foo 总是关于文件。
除了很好的答案。这个速记对我有用git revert --no-commit D C B
@welldan97:感谢您的评论。写这个答案时 git revert 不接受多次提交;这是一个相当新的补充。
r
rogerdpack

我发现有用的清洁方式

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

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

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

.. 有助于创建范围。含义 HEAD~3..HEAD~3..HEAD 相同


如果没有提交,这将如何工作?除了上面的命令还需要做什么?在此命令之前/之后需要哪些 git 命令?
@JohnLittle 它分阶段进行更改。 git commit 从那里实际执行提交。
如果某些提交是合并提交,这将不起作用。
最后的两个点有什么作用?
@cardamom 那些指定一个范围。 HEAD~3..HEAD~3..HEAD 相同
r
rogerdpack

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

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

git revert master~3..master

git revert B...Dgit revert D C B

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

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

git revert --no-commit HEAD~2.. 是一种更惯用的方式。如果您在 master 分支上,则无需再次指定 master。 --no-commit 选项让 git 尝试一次还原所有提交,而不是用多个 revert commit ... 消息乱扔历史记录(假设这是您想要的)。
@Victor 我修复了你的提交范围。范围的开头是独占的,这意味着它不包括在内。因此,如果要还原最后 3 次提交,则需要从第 3 次提交的 父项 开始,即 master~3
@kubi 是否无法使用单个提交(您的方法但无需手动输入已恢复的提交)将 SHA 包含在提交消息中?
@ChrisS我的第一个想法是不使用 --no-commit (因此您会为每个还原获得单独的提交),然后在交互式 rebase 中将它们全部压缩在一起。合并的提交消息将包含所有 SHA,您可以使用自己喜欢的提交消息编辑器随意排列它们。
果然,如果你在没有 --no-commit 的情况下这样做,它一次只做一个,你必须一遍又一遍地为每个输入 git revert --continue...当我希望 git 会有一个友好的提交选项,比如“做它们全部并在一次提交中为我列出所有哈希”但似乎不是:|
J
Jeff Ferland
git reset --hard a
git reset --mixed d
git commit

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


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

Jakub's answer 类似,这允许您轻松选择要恢复的连续提交。

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

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

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

然后:

git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply

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


不适用于二进制文件:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
这是一个比公认的答案更灵活的解决方案。谢谢!
回复:二进制文件使用 --binary 选项:git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
即使您想要还原包含合并提交的一系列提交,这也将起作用。使用 git revert A..Z 你会得到 error: commit X is a merge but no -m option was given.
哇酷,正是我要找的。所以它是反向差异,然后将这些更改应用于现有代码。非常聪明,谢谢。 ;)
P
Peter Mortensen

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

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

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

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

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


我还没有发布提交,所以这就是我需要的答案。谢谢,@Suamere。
更好的方法是 git push --force-with-lease,仅当在要蒸发的提交之后或范围内没有其他人提交到分支时,它才会重写历史记录。如果其他人使用了该分支,那么它的历史不应该被重写,并且提交应该简单地被明显地还原。
@frandroid“历史不应该被重写”,只有绝对的西斯交易。这个线程的问题,以及我回答的重点,正是对于特定的场景,所有的历史都应该被抹去。
@Suamere 当然,这就是问题所在。但正如你的回答提到你必须告诉其他人的事情,这可能会带来麻烦。根据个人经验,如果其他人在您尝试删除的内容后提交了代码库,则 push -f 可能会弄乱您的代码库。 --force-with-lease 达到了相同的结果,除了如果你要弄乱你的回购,它可以节省你的屁股。为什么要抓住机会?如果 --force-with-lease 失败,您可以查看哪个提交受到阻碍,正确评估、调整并重试。
@Suamere 谢谢!我同意这个问题清楚地表明它想改写历史。我和你处于同样的情况,我猜是因为有人意外地做了几十次丑陋的回复和奇怪的提交和回复的回复(当我在度假时),并且需要恢复状态。无论如何,连同一个良好的健康警告,这应该是公认的答案。
P
Peter Mortensen

这是对 Jakub's answer 中提供的解决方案之一的扩展。

我面临的情况是,我需要回滚的提交有些复杂,其中有几个提交是合并提交,我需要避免重写历史记录。我无法使用一系列 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>

应该没有区别。


你确定你可以用树枝的尖端git HEAD 吗?
这对我来说是一个更好的解决方案,因为我有合并提交。
r
rogerdpack

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

很好的答案,非常感谢,就像一个魅力!
只需添加:`^..`` 是包含范围。此外,出色的答案。它在 Git Bash 中对我有用。
D
Dorian

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

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

像魅力一样工作:)


仅当您的更改尚未推送时,这才是可行的选择。
m
meh

使用 Git 还原

您还可以使用 restore 命令:

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

假设您希望 HEAD 看起来与 A 完全一样。确保您已提取最新的 master。然后剪下一个新的分支。

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

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

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

文档:git restore


恢复方法对我来说干净而简单。
n
nulll

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

回到A

git checkout -f A

将主人的头指向当前状态

git symbolic-ref HEAD refs/heads/master

节省

git commit

你能解释一下拒绝投票的原因吗?
这工作正常。对这个有用的答案投反对票有什么意义,或者任何人都可以解释什么是最佳实践?
这和 git checkout master; git reset --hard A 一样吗?或者,如果不是,您能否进一步解释一下这是做什么的?
I
Ian

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

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


t
ti7

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

# 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 次,然后在另一个 git log HEAD~12..HEAD~10 中恢复最顶部的 2 次(12 是 7 次提交 + 5提交,假设新的 PR 分支基于它“之前”的分支,或者 FF(非压缩)将分支“之前”合并到原始目标分支的结果)


这比所有 --hard 命令要好,如果您要恢复提交,则应将其记录在历史记录中。
N
Nebulastic

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

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

S
ScottyBlades

如果你

有一个合并的提交,你不能恢复,你不介意压缩你要恢复的历史,

那么你也能

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

P
Peter Mortensen

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

Here is how it works

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