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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18082830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 08:52:47  来源:igfitidea点击:

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

gitgit-revert

提问by Leif

Context

语境

One of my teammates mistakenly pushed some commits to our main development branch. We're a small, collocated team. Our remote repository is hosted on an internal server.

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

Here is the top of our commit log (all these commits have already been pushed):

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

$ 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

da8b496is the last commit we wanted to keep in our developbranch, so we needed to revert the 5 last commits. We created a new branch from 8c29252to continue work in a "feature branch."

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

I tried many things, guided by this answerand this post from Linus, and ended up doing what you can see in my Terminal history below. But I'm not sure if what I ended up doing is "the right way." The information I found was complex; I was unable to discern a "best solution" for this particular problem.

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

Question

Was the approach I chose (see details below) a good way to revert those 5 commits, without harming our history? Is there an easier or "more correct" way to accomplish the same thing?

我选择的方法(参见下面的详细信息)是在不损害我们的历史的情况下恢复这 5 次提交的好方法吗?有没有更简单或更“更正确”的方法来完成同样的事情?

Amongst other things, I considered creating a new branch from da8b496(git checkout -b new-develop da8b496) and abandoning our current developbranch, but that just didn't feel right.

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



What I ended up doing (details)

我最终做了什么(详细信息)

First, I created a new branch for the commits a78b993and 8c29252, because these commits contain work that we want to keep and eventually merge back to our main development branch.

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

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

Then I started reverting the offending commits in our development branch.

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

I tried this first, but it didn't work (likely because some of the commits are merges):

我首先尝试了这个,但它没有用(可能是因为一些提交是合并的):

$ 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

So… I manually reverted each commit instead; one by one:

所以……我手动还原了每个提交;逐个:

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

Commit log after all the reverts were done:

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

$ 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

Graph after reverts:

还原后的图表:

$ 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

Seems correct to me. Lastly, I remove some backup files that I don't want to keep:

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

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

Current status is clean:

当前状态是干净的:

$ 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

And then I push everything back to the remote:

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

git push origin develop

采纳答案by jthill

You have a small co-located team, so communication isn't a problem. Make the commit history look as it should have looked:

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

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

and have everyone refetch. You're done.

并让每个人重新取回。你完成了。

This kind of mistake is one of the reasons to rewrite.

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

回答by Greg Bacon

Even though your history has changed, you can create branches that let you go back and experiment. Git means never having to say, “you should have.”If you converge on a reality you like better, then go with it. Otherwise, throw it away.

即使你的历史已经改变,你也可以创建分支,让你回去试验。Git 意味着永远不必说,“你应该有。” 如果你收敛于你更喜欢的现实,那就随它去吧。否则,扔掉它。

The examples below will create new branches that leave everything else in your repository alone.

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

Alternative 1: git revert

备选方案 1:git revert

First create a scratch branch at the point where you started your adventure.

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

$ git checkout -b tmp-revert faada93

By specifying a commit range, git revertwill undo multiple commits.

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

$ git revert da8b496..faada93

Alternative 2: git commit-tree

备选方案 2:git commit-tree

Consider the diagram below from Git Internals — Git Objects, section 10.2 in the second edition of Pro Gitby Scott Chacon and Ben Straub. The topmost commit (“third commit”) has a SHA1 hash that begins 1a410e. In the context of this history, 1a410e^{tree}would resolve to 3c4e9c, that is, the tree object immediately to the third commit's right.

考虑下面来自Git Internals — Git Objects的图表,Scott Chacon 和 Ben Straub在Pro Git第二版中的第 10.2 节。最上面的提交(“第三次提交”)有一个以 开头的 SHA1 哈希值1a410e。在此历史记录的上下文中,1a410e^{tree}将解析为3c4e9c,即紧接第三次提交右侧的树对象。

Git Object Graph, Figure 151 from *Pro Git*Figure 151 from Pro Git, 2nd ed.

Git 对象图,图 151 来自 *Pro Git*图 151 来自Pro Git,第二版。

Study this model to understand how git tracks content. Creating a new fourth commit whose tree is identical to the second commit's (that is, 0155eb) would add a new commit object that would shareor “point to” the existing tree and blobs rather than adding new duplicate objects.

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

Read on to learn how to perform this low-level stitching with git commit-tree.

继续阅读以了解如何使用git commit-tree.

Start by creating another temporary branch to work on.

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

$ git checkout -b tmp-ctree faada93

At this point, you want to create a new commit where its tree (that is, the committed code) is identical to that of da8b496, the last commit you wanted to keep. This tree is directly addressable in git: da8b496^{tree}.

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

git commit-treeis “plumbing,” a low-level command in git—as opposed to “porcelain.” It may feel awkward or unfamiliar to use, but in this case it gives precise control of the result you want.

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

Create a new unattached commit whose tree is the same as da8b496's and whose parent (-p) is the tip of the current branch, faada93in your case. Note that git commit-treereads the commit message of the new commit on the standard input, which the command below supplies with the echocommand.

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

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

The italicized portion above is not part of the command. It indicates that git commit-treeoutputs the SHA1 hash of the newly created commit. Knowing the new commit's SHA1, you can move the branch to that point, e.g.,

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

$ git merge new-commit-sha1

In the command above, replace new-commit-sha1with the output from git commit-tree. (You could do the same git reset --hard new-commit-sha1, but hard reset is a sharp tool where casual use is best avoided.)

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

You could roll all of the above into a single compound command.

您可以将上述所有内容合并为一个复合命令。

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

The --ff-onlyswitch to git mergeis meant to prevent surprises. Your intent is for the new commit to be a fast-forward or a descendant of the current branch head—its immediate child, in fact!

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

Cleanup

清理

To delete the temporary branches above, switch to another and fire away, Mr. McManus. Your other branches will be just as you left them.

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

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

The two should be identical, as you can verify with

两者应该相同,因为您可以验证

$ git diff tmp-revert tmp-ctree

To keep one, merge it into your developbranch.

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

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

回答by Chris Conover

May I suggest that this could be considered a duplicate of this answer: Make the current git branch a master branch

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

Jefromi's excellent solution was:

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

回答by CodeWizard

What you are trying to do is very risky.

你试图做的事情是非常冒险的。

indeed you can revert and delete the commits that you have already pushed to the repo but if someone has already pulled your changes and he has the commitId that you are going to delete, the repo can become "unstable" and git will not be able to handle the pull and pushes since you deleted commit that is now removed from the history.

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

Do this (revert and delete commit ) only and only if no one has pull this commit yet.

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