【问题标题】:Reverting a series of pushed merges and commits in Git (without rewriting history)在 Git 中恢复一系列推送的合并和提交(不重写历史记录)
【发布时间】:2013-08-07 14:47:21
【问题描述】:

上下文

我的一个队友错误地将一些提交推送到我们的主要开发分支。我们是一个小型的、并置的团队。我们的远程存储库托管在内部服务器上。

这是我们提交日志的顶部(所有这些提交都已被推送):

$ git log develop -6 --pretty=oneline --abbrev-commit
faada93 Merge branch 'develop' of <our_repo_path>.git
244d174 Support classes again
a97a877 Pruned all unused references (again).
8c29252 Merge branch 'develop' of <our_repo_path>.git
a78b993 Support models & methods - product types & categories
da8b496 Resolved JIRA issue PPF-182

da8b496 是我们想要保留在 develop 分支中的最后一个提交,因此我们需要恢复最后 5 个提交。我们从 8c29252 创建了一个新分支,以继续在“功能分支”中工作。

this answerthis post from Linus 的指导下,我尝试了很多事情,最终完成了您可以在下面的终端历史记录中看到的内容。但我不确定我最终做的是否是“正确的方式”。我发现的信息很复杂;对于这个特定问题,我无法找出“最佳解决方案”。

问题

我选择的方法(见下文)是恢复这 5 次提交而不损害我们历史记录的好方法吗?有没有更简单或更“正确”的方法来完成同样的事情?

除此之外,我考虑过从da8b496 (git checkout -b new-develop da8b496) 创建一个新分支并放弃我们当前的develop 分支,但感觉不对。


我最终做了什么(详情)

首先,我为提交 a78b9938c29252 创建了一个新分支,因为这些提交包含我们想要保留并最终合并回我们的主要开发分支的工作。

$ git checkout -b new-feature-brach 8c29252

然后我开始在我们的开发分支中恢复有问题的提交。

我先尝试了这个,但没有成功(可能是因为某些提交是合并):

$ git revert a78b993..HEAD
error: a cherry-pick or revert is already in progress
hint: try "git cherry-pick (--continue | --quit | --abort)"
fatal: revert failed

所以……我改为手动还原每个提交;一一:

$ git revert -m 1 faada93
[develop 40965a5] Revert "Merge branch 'develop' of <our_repo_path>.git"
8 files changed, 167 insertions(+), 3 deletions(-)

$ git revert 244d174
[develop 3cebd68] Revert "Support classes again"
45 files changed, 557 insertions(+), 1572 deletions(-)
(list of affected files)

$ git revert a97a877
error: could not revert a97a877... Pruned all unused references (again).
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

$ git mergetool
Merging:
exampleFile1.cs
exampleFile2.cs

Deleted merge conflict for 'exampleFile1.cs':
{local}: deleted
{remote}: modified file
Use (m)odified or (d)eleted file, or (a)bort? m

Deleted merge conflict for 'exampleFile2.cs':
{local}: deleted
{remote}: modified file
Use (m)odified or (d)eleted file, or (a)bort? m

$ git commit -m "Adding files to be reverted along with the next commit."
[develop 15bc02b] Adding files to be able to revert the next commit in line.
2 files changed, 239 insertions(+)
(list of affected files here)

$ git revert -m 1 8c29252
# On branch develop
# Your branch is ahead of 'origin/develop' by 3 commits.
#   (use "git push" to publish your local commits)
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       exampleFile1.cs.orig
#       exampleFile2.cs.orig
nothing added to commit but untracked files present (use "git add" to track)

$ git revert a78b993
[develop 841e77c] Revert "Support models & methods - product types & categories"
2 files changed, 239 deletions(-)
(list of affected files here)

所有还原完成后提交日志:

$ git log develop -10 --pretty=oneline --abbrev-commit
841e77c Revert "Support models & methods - product types & categories"
15bc02b Adding files to be able to revert the next commit in line.
3cebd68 Revert "Support classes again"
40965a5 Revert "Merge branch 'develop' of <our_repo_path>.git"
faada93 Merge branch 'develop' of <our_repo_path>.git
244d174 Support classes again
a97a877 Pruned all unused references (again).
8c29252 Merge branch 'develop' of <our_repo_path>.git
a78b993 Support models & methods - product types & categories
da8b496 Resolved JIRA issue PPF-182

还原后的图表:

$ git log --graph --oneline -8 develop
* 841e77c Revert "Support models & methods - product types & categories"
* 15bc02b Adding files to be able to revert the next commit in line.
* 3cebd68 Revert "Support classes again"
* 40965a5 Revert "Merge branch 'develop' of <our_repo_path>.git"
*   faada93 Merge branch 'develop' of <our_repo_path>.git
|\
| * a97a877 Pruned all unused references (again).
| *   8c29252 Merge branch 'develop' of <our_repo_path>.git
| |\
| | * da8b496 Resolved JIRA issue PPF-182

对我来说似乎是正确的。最后,我删除了一些我不想保留的备份文件:

$ git clean -fd
(list of affected files here)

当前状态是干净的:

$ git status
# On branch develop
# Your branch is ahead of 'origin/develop' by 4 commits.
#   (use "git push" to publish your local commits)
#
nothing to commit, working directory clean

然后我将所有内容推回遥控器:

git push origin develop

【问题讨论】:

  • 请显示git log --graph --oneline develop的输出。此外,假设faada93 是合并develop 的位置,您需要做的就是还原一次提交,并且其所有不属于当前分支的子项也将被还原,您不需要恢复任何其他的。你所做的看起来比必要的要复杂得多。
  • 谢谢,@Cupcake - 我将图表添加到问题中。我对问题中的哈希值犯了一些令人困惑的错误;我现在会更正它们。
  • 这个仓库是公开的,还是一个小团队共享的私有仓库?如果是后一种情况,是否有理由不将分支硬重置到合并前的点?
  • 这是一个私有存储库,托管在内部服务器上,由一小群开发人员使用。是否可以使用“硬重置”,而不会对我们的分支历史产生不利影响?我们都是 Git 的新手,只是觉得还原是一种更安全、“更正确”的方式。我们发现的信息充其量是令人困惑的,因此这个问题:)
  • 我在Git chat room,如果您想加入我,我们可以实时讨论您的问题。哦,等等,对不起,那个房间真的冻坏了,让我试试别的。

标签: git git-revert


【解决方案1】:

即使您的历史发生了变化,您也可以创建让您返回并进行实验的分支。 Git means never having to say, “you should have.”如果你收敛到你更喜欢的现实,那就去吧。否则,扔掉它。

下面的示例将创建新的分支,将其他所有内容单独留在您的存储库中。

备选方案 1:git revert

首先在您开始冒险的地方创建一个临时分支。

$ git checkout -b tmp-revert faada93

通过指定提交范围,git revert 将撤消多个提交。

$ git revert da8b496..faada93

备选方案 2:git commit-tree

考虑下图Git Internals — Git ObjectsPro Git 第二版中的第 10.2 节,作者 Scott Chacon 和 Ben Straub。最顶层的提交(“第三次提交”)有一个以 1a410e 开头的 SHA1 哈希。在这段历史的上下文中,1a410e^{tree} 将解析为3c4e9c,即紧靠第三次提交右侧的树对象。

图 151 来自Pro Git,第 2 版。

研究此模型以了解 git 如何跟踪内容。创建一个新的第四个提交,其树与第二个提交的相同(即0155eb)将添加一个新的提交对象,该对象将共享或“指向”现有的树和 blob,而不是添加新的重复对象。

继续阅读以了解如何使用git commit-tree 执行这种低级拼接。

首先创建另一个临时分支来处理。

$ git checkout -b tmp-ctree faada93

此时,您想要创建一个新的提交,其树(即提交的代码)与 da8b496 的树相同,这是您要保留的最后一个提交。这棵树可以在 git 中直接寻址:da8b496^{tree}

git commit-tree 是“管道”,是 git 中的一个低级命令,与“瓷器”相对。使用起来可能会感到尴尬或不熟悉,但在这种情况下,它可以精确控制您想要的结果。

创建一个新的未附加提交,其树与 da8b496 相同,其父 (-p) 是当前分支的尖端,在您的情况下为 faada93。请注意,git commit-tree 在标准输入上读取新提交的提交消息,下面的命令与 echo 命令一起提供。

$ echo 恢复到 da8b496 | \
    git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree)
new-commit-sha1

上面的斜体部分不是命令的一部分。它表示git commit-tree 输出新创建的提交的SHA1 哈希。知道新提交的 SHA1,您可以将分支移动到该点,例如

$ git merge new-commit-sha1

在上面的命令中,将 new-commit-sha1 替换为 git commit-tree 的输出。 (你也可以这样做git reset --hard new-commit-sha1,但硬重置是一种锋利的工具,最好避免随意使用。)

您可以将以上所有内容整合到一个复合命令中。

$ git merge --ff-only $(echo Revert back to da8b496 | \
    git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree))

--ff-only 切换到 git merge 是为了防止意外。您的意图是让新提交成为当前分支头的快进或后代——事实上,它的直接子代!

清理

要删除上面的临时分支,请切换到另一个并开火,McManus 先生。您的其他分支将与您离开时一样。

$ git checkout 开发
$ git branch -D tmp-revert tmp-ctree

这两个应该是相同的,你可以验证

$ git diff tmp-revert tmp-ctree

要保留一个,请将其合并到您的 develop 分支中。

$ git checkout 开发
$ git merge --ff-only tmp-ctree
$ git push origin 开发

【讨论】:

  • 考虑到当前版本的 Git 已经附带的内置命令,这似乎比必要的要复杂得多。为什么不直接使用硬重置或还原?
  • 感谢您的回答,格雷格。在我敢运行它之前,我需要花更多时间来了解它的确切作用。此外,我们在基于8c29252 的不同分支中进行了一些工作;我用这些信息更新了我的问题,以防它有所作为。
  • 再次感谢 Greg 的详细回答。我将花一些时间研究这些选项并了解更多关于它们如何工作的信息,尤其是commit-tree。由于即将到来的客户演示,我现在没有足够的时间坐下来处理这个问题,需要快速对其进行整理,所以我选择了另一个答案。 (在我完全理解它们的作用之前,我不喜欢运行任何命令。)
  • commit-tree 赢得胜利!谢谢。
  • 运行上面的git commit-tree命令时,上面的echo命令很重要。我尝试跳过它,命令似乎只是挂起。这是因为它正在等待提交消息,然后是 Ctrl+D 来完成。管道echo 的结果跳过这一步。
【解决方案2】:

我可以建议这可以被视为此答案的重复:Make the current git branch a master branch

Jefromi 的出色解决方案是:

[git branch better_branch <last good commit>]
git checkout better_branch
git merge --strategy=ours master    # keep the content of this branch, but record a merge
git checkout master
git merge better_branch             # fast-forward master up to the merge

【讨论】:

    【解决方案3】:

    您有一个小型的同地团队,因此沟通不成问题。使提交历史看起来像它应该的样子:

    git branch -f develop dab4896
    git branch newfeature 8c29252
    git push -f origin develop newfeature
    

    并让每个人重新获取。你已经完成了。

    这种错误是重写的原因之一。

    【讨论】:

    • 谢谢,@jthill。最后一个命令究竟做了什么,取了 3 个分支名称?不久前我已经从8c29252 建立了一个分支,人们已经提交了新的东西并将其推送到该分支,所以我不想再次重置“newfeature”分支。
    • 谢谢!干净简单。我做了git branch -f develop da8b496git push -f origin develop,然后请大家做git fetch。像魅力一样工作。
    【解决方案4】:

    你正在尝试做的事情是非常危险的。

    确实,您可以恢复并删除您已经推送到存储库的提交,但是如果有人已经拉取了您的更改并且他拥有您要删除的 commitId,那么存储库可能会变得“不稳定”并且 git 不会能够处理拉取和推送,因为您删除了现在从历史记录中删除的提交。

    仅在没有人拉取此提交时才执行此操作(恢复和删除提交)。

    【讨论】:

    • 这就是我最终选择正常还原提交的原因。您是说我选择的方法(手动还原每个提交)有风险吗?我没有删除任何提交;我只恢复了它们。 (不知道我是否理解)。
    猜你喜欢
    • 2011-11-08
    • 1970-01-01
    • 2013-03-24
    • 2014-05-14
    • 2022-01-18
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多