在Git中恢复一系列推送的合并和提交(无需重写历史记录)

Lei*_*eif 23 git git-revert

上下文

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

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

$ 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
Run Code Online (Sandbox Code Playgroud)

da8b496是我们想要保留在develop分支中的最后一个提交,所以我们需要还原最后的5个提交.我们创建了一个新分支,8c29252以继续在"功能分支"中工作.

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

我选择的方法(见下面的详细信息)是否能够在不损害我们历史的情况下还原这5个提交?有没有更简单或"更正确"的方法来完成同样的事情?

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


我最终做了什么(细节)

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

$ git checkout -b new-feature-brach 8c29252
Run Code Online (Sandbox Code Playgroud)

然后我开始在我们的开发分支中恢复违规提交.

我首先尝试了这个,但它不起作用(可能是因为某些提交是合并的):

$ 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
Run Code Online (Sandbox Code Playgroud)

所以...我手动恢复每个提交; 逐一:

$ 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)
Run Code Online (Sandbox Code Playgroud)

完成所有恢复后提交日志:

$ 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
Run Code Online (Sandbox Code Playgroud)

恢复后的图形:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

$ git clean -fd
(list of affected files here)
Run Code Online (Sandbox Code Playgroud)

目前的状况是干净的:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

git push origin develop
Run Code Online (Sandbox Code Playgroud)

Gre*_*con 11

即使您的历史记录发生了变化,您也可以创建分支,让您可以返回并进行实验.Git意味着永远不必说,"你应该拥有."如果你想要更好地融入现实,那就去吧.否则,扔掉它.

下面的示例将创建新的分支,只留下存储库中的其他所有内容.

备选方案1:git revert

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

$ git checkout -b tmp-revert faada93

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

$ git revert da8b496..faada93

备选方案2:git commit-tree

请考虑下面Git Internals - Git Objects的图表,Scott Chacon和Ben Straub 的第二版Pro Git中的10.2节.最顶层的提交("第三次提交")具有开始的SHA1哈希1a410e.在此历史的上下文中,1a410e^{tree}将立即解析为3c4e9c树对象到第三个提交的权限.

Git Object Graph,图151来自*Pro Git* Pro Git的图151 ,第2版.

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

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

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

$ git checkout -b tmp-ctree faada93

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

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

在您的情况下,创建一个新的未附加提交,其树与da8b496s 的相同,其parent(-p)是当前分支的tip faada93.请注意,git commit-tree在标准输入上读取新提交的提交消息,以下echo命令随命令提供.

$ echo Revert back to 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是为了防止意外.你的意图是让新的提交成为当前分支头的快进或后代 - 实际上是它的直接孩子!

清理

为了删除上面的临时分支,切换到另一个并开火,麦克马纳斯先生.你的其他分支就像你离开它们一样.

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

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

$ git diff tmp-revert tmp-ctree

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

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


jth*_*ill 9

你有一个小型的共处团队,所以沟通不是问题.使提交历史记录看起来应该看起来:

git branch -f develop dab4896
git branch newfeature 8c29252
git push -f origin develop newfeature
Run Code Online (Sandbox Code Playgroud)

并让每个人都重新获得.你完成了.

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


Chr*_*ver 5

我可以建议这可以被认为是这个答案的重复: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
Run Code Online (Sandbox Code Playgroud)