29

语境

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

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

$ 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因为这些提交包含我们想要保留并最终合并回我们的主要开发分支的工作。

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

4 回答 4

13

即使您的历史发生了变化,您也可以创建让您返回并进行实验的分支。Git 意味着永远不必说“你应该拥有”。</a> 如果你收敛到你更喜欢的现实,那就去吧。否则,扔掉它。

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

备选方案 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 对象图,来自 *Pro Git* 的图 151 图 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's 相同,其父 ( -p) 是当前分支的尖端faada93。请注意,git commit-tree在标准输入上读取新提交的提交消息,下面的命令随echo命令一起提供。

$ echo 恢复到 da8b496 | \
    git commit-tree da8b496^{tree} -p $(git rev-parse tmp-ctree)
新提交-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 恢复到 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 开发
于 2013-08-06T14:39:27.213 回答
11

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

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

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

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

于 2013-08-07T15:44:31.497 回答
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
于 2016-02-24T00:59:37.850 回答
0

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

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

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

于 2013-08-06T20:47:09.660 回答