git 将远程恢复到带有合并的特定提交不适用于 -m

git revert remote to a particular commit with a merge doesn't work with -m

我正在尝试将我们的远程回购恢复到之前的提交。树如下图:

我们的想法是,我们希望回到那个提交,而不需要任何从 master 分支中拉出来的人必须处理因重置回提交 2dda031 而产生的问题。所以我改用 git revert

我正在尝试使用 git revert --no-commit 2dda031..HEAD

来做到这一点

但是,我得到这个错误:

error: commit d064f7c3b04a2bda30c43a32afac822c6af633c0 is a merge but no -m  option was given. 
fatal: revert failed

这是预期的,因为 d064f7c 是合并(47d4161 也是)。所以按照建议 here 我这样做:

git revert --abort
git revert --no-commit -m 1 2dda031..HEAD

然后我收到错误消息:

error: mainline was specified but commit cb420e0 is not a merge.
fatal: revert failed

所以我只是觉得我在循环。有人可以告诉我恢复到该提交的正确方法(同时恢复历史记录)吗?

编辑(下面是原始答案):让我首先将您的图形转换为文本,(我希望)没有拼写错误或其他严重错误。这就是您现在所拥有的,正如 git log --graph --oneline 可能显示的那样(虽然 --graph --oneline 可能会选择稍微不同的提交顺序——图形查看器生成的蓝线和绿线可能按提交日期排序,而不考虑拓扑,而不是先按拓扑排序):

* cb420e0  (master, ...) evert "Update README.md"
* 7a16df4  Update README.md
* 7564754  Update README.md
* 214cd47  Update README.md
* d064f7c  Merge pull request #6 from ...
|\
* | d936a24  Changing Run instructions
* | 2cbd7c2  Minor edits for Google Drive link
* | 1a3d871  Updated process documentation with google drive link
| * 0594132  (TrustM..., ...) Added some comments to various scripts.
| * 7e060c4  Updated the JSON dialogues and implemented the Trust mechanism
|/
* 4d7f49b  Configured script inputs and enabled mouse during pause screen.
* 47d4161  Merge pull request #5 from ...
|\
| * e999b3d  (origin/Trying...) Adjusted ray cast length to be more realistic.
| * 953e4c3  Fully functional dialogue system implemented.
* | 1f33079  updated wiki to reflect marking of prototype
| * 09e350b  Added in most of the Yarn framework
| * 2dda031  fixed heirarchy of files
| * bf667cc  Merge branch 'develop' of ...
| |\
| * |  79e068d  Character placement

(我们看不到低于此点的任何内容,但显然必须有更多提交)。

我猜你想要返回的提交状态是 2dda031 fixed heirarchy of files

现在,最棘手的部分是这个状态 "lives on" what was apparently a side branch, under the "Merge pull request #5 from ..." 犯罪。如果在还原过程中的某个时刻,您要 运行 git revert -m <some-number> 47d4161,您将告诉 Git 将 47d4161 与其第一个 parent、1ff3079,或者反对它的第二个 parent,d999b3d。这些差异中的第一个显示了自合并基础以来每次提交的效果——无论是什么提交:我们无法从图表的这个片段中看到它;我们需要更多图表来找到它,因为它位于此处 "screen" 的底部 - 这两个中的一个,另一个差异显示了自合并基础到另一个的每次提交的效果。所以用 -m 1 恢复基本上消除了以下效果:

* e999b3d  (origin/Trying...) Adjusted ray cast length to be more realistic.
* 953e4c3  Fully functional dialogue system implemented.
* 09e350b  Added in most of the Yarn framework
* 2dda031  fixed heirarchy of files
* bf667cc  Merge branch 'develop' of ...
...

而使用 -m 2 基本上消除了以下效果:

* 1f33079  updated wiki to reflect marking of prototype
...

(在这两种情况下,可能会有更多我们在这里看不到的提交)。我很清楚你不想那样做,但我不确定。

但是请注意,如果您只是提取提交 2dda031 的内容,您 仍然 失去 1f33079 的效果以及任何其他提交可能低于它,因为你在 origin/Trying....

之前获得了几次提交的状态

关于提交 d064f7c,事情更简单,因为它只是从合并基础 4d7f49b 合并 d936a240594132。如果你想撤销提交 1a3d871+2cbd7c2+d936a24 的影响,你可以 git revert -m 1 d064f7c。如果您想撤消提交 0594132+7e060c4 的影响,您可以 git revert -m 2 d064f7c。但是,由于您(大概)想要撤消其中 所有 的影响,因此更简单的方法是单独撤消它们,完全跳过合并。

由于您可能还想通过 214cd47 撤消 cb420e0,您只需单独撤消这些。

如果您想保持 1f33079 和更早提交的效果,请不要还原它们。如果你想撤销它们的效果,你可以git revert -m 2 47d4161,它会一次性全部撤销。我怀疑你想要那个,但和以前一样,这取决于你。

请注意,任何还原都可以是 运行 和 -n(在索引和 work-tree 中还原而不提交),但是一旦你开始了一系列 -n操作,您必须继续 -n 并最终提交,然后才能在没有 -n.

的情况下开始新的还原

根据您想要的结果,最简单的方法可能是从提取提交 47d4161 内容 开始——树状态最早可追溯到你可以不做单独的 commit-backouts——然后在每个额外的提交上使用 git revert -n 来恢复。要提取这些内容,您可以使用 git checkout <commit> -- . 来避免删除索引和 work-tree 条目的轻微风险,因为那时是新的文件,或者使用 git read-tree --reset -u <commit> 来避免风险(见备注下面,也是原始答案)。我会选择后者,给出:

git read-tree --reset -u 47d4161
git revert -n e999b3d
git revert -n 953e4c3
git revert -n 09e350b

(假设你得到了最终的 work-tree 并且索引表明你当然想要拥有)。

(原始答案在下方。)


Git 的 revert 不会将 还原为 提交(这使得命令命名不当:它使用了错误的动词)。它所做的是退出(即"revert")一个特定的提交,或者可能是一组提交。1 你已经完成了 2dda031..HEAD,因为这个范围语法实际上意味着 HEAD ^2dda031,即从 HEAD 可以到达的所有提交的集合,不包括(减去, set-subtraction) 可从 2dda031.

到达的所有提交集

现在,由于合并,这里有多个问题。首先是排除 2dda031 及其 parents 无法排除合并的另一部分,因此您将还原太多提交。第二个问题是,从某种意义上说,合并提交是一个提交,随着它的 single-commit 改变,"all of the changes being brought in by a side branch".2 第三个是在为了恢复合并提交,您必须指定要考虑的"side",但为了恢复non-merge,您不能 指定任何 "side".

其中一些问题的解决方案是完全避免还原合并,而其他问题的解决方案是 还原合并(如果适用)。但还有另一种更简单的方法,具体取决于您的实际目标:如果真的要转rt 提交,Git 动词实际上是 git checkout——但是有一些陷阱。请参阅 this answer 相关(甚至可能重复,具体取决于您的目标)问题。 git rm -r . 的原因是删除当前索引中不会被 git checkout <hash> -- . 步骤提取的所有文件。

有一个 short-cut 可以用来代替 git rm -r . && git checkout <hash> -- . 序列,它也不依赖于当前的工作目录:您可以 运行 git read-tree --reset -u <hash>。这将丢弃索引的当前内容 (--reset) 并将它们替换为指定提交的内容 (<hash> 参数),然后更新 work-tree 以匹配,删除任何文件从索引中删除并更新索引中更新的任何文件。

请注意,在所有情况下,最终结果都在索引和 work-tree 中,但尚未提交,因此您必须 运行 git commit.

请注意,如果您的目标实际上不是将 还原为 特定提交,而是还原引入的一系列更改,则方法为git revert -n 是根据需要使用尽可能多的单独 git revert 命令:每个 change-set 一个要退出的命令。其中一些可能是 git reverts of non-merges,有些可能是 git reverts of merges。不过请参阅脚注 2,并记住无论您使用哪种方法,退出您想要保留的更改毕竟会成功(更改将消失),即使您想要保留它也是如此。


1因此有些VCS里的动词是"backout".

2这个描述在一个微妙但非常重要的方面是错误的:合并 combines 变化。合并的输入是两组更改:一组从合并基础提交到 --ours 提交,另一组从合并基础提交到 --theirs 提交。这两个 change-set 可能会重叠。如果他们这样做,并且重叠在任何时候都是 "sufficiently similar",则 Git 只需要 一个副本 的变化。如果 Git 复制了某个更改 Δ,其中 Δ 出现在两个 change-set 中,并且您恢复 --theirs change-set,Git 退出 Δ 即使它也在 --ours change-set 中。(如果您取消 --ours 增量,同样的推理也适用:Git不应该,但确实将其从作为 --theirs 引入的版本中删除。)