git revert 要“退出”更改,需要弄清楚更改是什么。
在大多数普通提交的情况下,更改很容易计算。例如考虑这个 git commit graph 片段:
... - G - H ... <-- HEAD=master
在这里,您位于分支 master,其中包含提交 G、H,然后还有更多。
如果您要求 git 恢复提交 H,它只需要查看“修订版 G 中的所有内容”和“修订版 H 中的所有内容”之间的变化。通过比较G 和H,Git 可以像你一样做到这一点:
$ git diff <sha1-of-G> <sha1-of-H>
如果这表明在提交 H 中,您向文件 readme.txt 添加了一行并完全删除了文件 x.h,那么 git 可以通过从 readme.txt 中删除该行并恢复文件 x.h 来撤消此操作来自提交G。
不过,合并提交更复杂。让我们再填写一些提交图:
I - J
/ \
... - G - H M - N - O <-- HEAD=master
\ /
K - L
如果你要求 git 恢复合并提交 M,它应该撤回哪些更改?
从J 到M 有一组变化:
$ git diff <sha1-of-J> <sha1-of-M>
(实际上,这些更改是通过提交L 带来的更改,与提交H 相比,这将是提交K 和L 组合的更改。
从L 到M 还有另一组可能完全不同的更改:
$ git diff <sha1-of-L> <sha1-of-M>
(这些变化实际上是I和J中的变化,逻辑相似)。
您必须告诉 git 要撤消哪些组更改,以及要保留哪些更改。 Git 让你通过指定“主线”来做到这一点。这还依赖于存储在合并提交中的父 ID按特定顺序这一事实。
假设您在进行合并时正在提交 J,即 master:
$ git checkout master # i.e., commit J
$ git merge branch # i.e., commit L
现在你正在提交O,它仍然是master。分支名称branch 可能不再存在(或可能指向除L 之外的某个提交),但您想丢弃K 和L 中的更改——即,那些在分支@ 上的更改987654361@ 进行合并时。
M 的 第一个 父级是 J,因为您将 branch 合并到 master 中,其中 git 通过将 J 设为第一个父级并将 L 设为第二个父级来记录父母。因此,要丢弃来自提交 K 和 L 的更改,您现在可以使用:
$ git revert -m 1 HEAD~2
(这里HEAD~2 备份两个提交,从O 到N,然后到M)。然后,Git 可以区分 M^1 (J) 和 M,它发现通过合并分支 branch 引入的更改,如上所述;然后撤消这些更改会导致撤消合并引入的更改。
请注意,这会生成一个新的提交,生成的图形如下所示:
I - J
/ \
... - G - H M - N - O - P <-- HEAD=master
\ /
K - L
比较提交O 和P 产生与比较M 和J 基本相同的结果(按此顺序,即从J 到M 的“正常”比较相反)。但是,就 git 中的后续操作而言,您最好通过手动编辑 O 的树并进行新的提交 P 来完成此操作:它不记录(提交消息文本除外) P 本质上是 K 和 L 的还原。
顺便提一下,在这种特殊情况下,您可以简单地先恢复L,然后恢复K,以(可能)获得相同的效果(有两个单独的额外提交):
$ L=$(git rev-parse HEAD~2^2) # get sha-1 ID of commit L
$ git revert $L # make new commit P that reverts L
$ git revert $L^ # make new commit Q that reverts L^ = K
不过,在大合并的情况下,还原每个单独的更改是一项繁重的工作;恢复合并提交本身要容易得多(如果有适当的文档,则可以做到,并在以后理解)。 (此外,上面的“可能”是因为合并处理了在分支的“两侧”所做的相同更改,并且恢复合并避免撤消 K 和 L 中的更改 不是带入M,因为它们也出现在I 和/或J。然而,这有点罕见,尤其是在像这样的微小分支结构中。)