关于git revert:在Git中恢复一系列推送的合并和提交(不重写历史记录)

Reverting a series of pushed merges and commits in Git (without rewriting history)

上下文

我的一个队友错误地把一些承诺推到了我们的主要开发部门。我们是一个小型的、并置的团队。我们的远程存储库托管在一个内部服务器上。

下面是提交日志的顶部(所有这些提交都已推送):

1
2
3
4
5
6
7
$ 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创建了一个新的分支,以继续在"功能分支"中工作。

我尝试了很多事情,在这个答案和Linus的这篇文章的指导下,最终做了你可以在我下面的终端历史中看到的事情。但我不确定我最终所做的是否是"正确的方式"。我发现的信息很复杂;我无法为这个特定的问题找到"最佳解决方案"。

问题

我选择的方法(见下面的详细信息)是否是在不损害我们历史的情况下恢复这5项承诺的一个好方法?有没有一种更容易或"更正确"的方法来完成相同的事情?

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

我最后做了什么(细节)

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

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

然后我开始恢复我们开发部门的违规行为。

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

1
2
3
4
$ 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

所以…我手动恢复了每个提交;一个接一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$ 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)

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

1
2
3
4
5
6
7
8
9
10
11
$ 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

恢复后图形:

1
2
3
4
5
6
7
8
9
10
11
$ 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

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

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

当前状态为清除:

1
2
3
4
5
6
$ 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

然后我把所有东西都推回到遥控器上:

1
git push origin develop


即使您的历史已经改变了,您也可以创建一些分支,让您返回并进行实验。Git的意思是不必说,"你应该这样做。"如果你集中在一个你更喜欢的现实上,那就去做吧。否则,扔掉它。

下面的示例将创建新的分支,使您的存储库中的其他内容保持独立。

备选方案1:Git还原

首先在你开始冒险的地方创建一个划痕分支。

1
$ git checkout -b tmp-revert faada93

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

1
$ git revert da8b496..faada93

号备选方案2:Git提交树

下面的图表来自Git内部-Git对象,由Scott Chacon和Ben Straub编写的第二版Pro Git第10.2节。最上面的commit("第三个commit")有一个以1a410e开头的sha1哈希。在这一历史背景下,1a410e^{tree}会向3c4e9c下决心,即树立即反对第三个承诺的权利。

Git Object Graph, Figure 151 from *Pro Git*。图151摘自《出版自由》,第2版。

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

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

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

1
$ git checkout -b tmp-ctree faada93

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

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

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

1
2
3
$ echo Revert back to da8b496 | \
    git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree)
new-commit-sha1

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

1
$ git merge new-commit-sha1

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

您可以将上述所有内容都滚动到一个复合命令中。

1
2
$ 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先生。你的其他分支将和你离开它们时一样。

1
2
$ git checkout develop
$ git branch -D tmp-revert tmp-ctree

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

1
$ git diff tmp-revert tmp-ctree

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

1
2
3
$ git checkout develop
$ git merge --ff-only tmp-ctree
$ git push origin develop


你有一个很小的团队,所以沟通不是问题。使提交历史看起来像它应该看到的那样:

1
2
3
git branch -f develop dab4896
git branch newfeature 8c29252
git push -f origin develop newfeature

让所有人重修一遍。你完了。

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


我可以建议这可以被视为这个答案的副本:使当前的git分支成为一个主分支

Jefromi的最佳解决方案是:

1
2
3
4
5
[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

你想做的事风险很大。

实际上,您可以恢复和删除已经推送到回购的提交,但是如果有人已经拉走了您的更改,并且他拥有您要删除的提交ID,那么回购可能会变得"不稳定",并且Git将无法处理拉入和推送,因为您删除了现在已从历史记录中删除的提交。

仅当还没有人执行此提交时才执行此操作(还原和删除提交)。