TT Lab
开始
学习 学习路径 课程

Git 实战

被回退的功能,再 merge 一次也回不来

在 TT Lab 中继续学习

一句话总结

revert 会创建一个把改动反向抵消的新提交。因此,要撤销一个合并提交,必须指明以哪一侧为基准来抵消(-m);而被撤销的功能分支以后再次 merge 时,git 会回答“已经合并过了”。因为历史还在,只是内容消失了。

为什么需要它

发布的第二天出了故障。原因是昨天合并的功能分支,而且已经有很多人拉取了那段历史。不能用 reset——那等于删除已经传到别人手里的提交,所有人的历史都会错位。所以要用 revert。

然而,当你想撤销那个合并提交时,git 会停下来。

$ git revert 685e75f
error: commit 685e75f is a merge but no -m option was given.
fatal: revert failed

合并提交有两个父提交。“撤销”意味着回到某个状态,但有两个父提交时,回到哪个父提交的状态就无法确定。-m 1 表示以第一个父提交(通常是被合并进去的一侧,也就是 main)为基准,去抵消第二个父提交带来的改动。-m 2 则相反。

到这里,大多数人都是知道的。真正的陷阱在后面。

工作原理

用 revert -m 1 撤销合并之后,修好功能想再次合入,执行 git merge feature,会得到这样的输出。

Already up to date.

这看起来像 bug,其实是准确的回答。merge 依据的是历史的可达性。那个合并提交仍在历史中,feature 的所有提交从那里都可达,所以在 merge 看来,已经没有需要合并的东西了。之所以没有内容,是因为后面的 revert 提交把它抹掉了,而不是因为没合并过。

因此,恢复被撤销的功能,办法是对 revert 再做一次 revert。

git revert <되돌림 커밋>

git 2.x 会给这个提交自动加上默认标题 Reapply "..."。历史中同时保留撤销和恢复两条记录,以后想知道“这个功能为什么进来了两次”,只读历史就能明白。Linux 内核文档推荐这种模式,理由也一样——历史应当如实讲述实际发生过的事。

给 cherry-pick 留下出处

在发布分支上做热修复,再把它也合入 main,是很常见的事。直接 cherry-pick 的话,新提交里不会留下原提交来自哪里。加上 -x,提交消息的末尾会多出一行。

(cherry picked from commit fb42e70c24e9bab398dcef64c3b2bd316de8ca4e)

以后调查“这个修复有没有也进入发布分支”时,有没有这一行差别很大。不过从私有仓库迁到公开仓库时,它会指向一个不存在的哈希,那时就不要使用。

相同的改动是否已经合入——patch-id

“这个提交是否已经合入那个分支”,光看哈希是看不出来的,因为 cherry-pick 会创建新的提交。git 改用 patch-id——它是把 diff 中的空白和行号去掉之后计算出的哈希,所以只要是相同的改动,即使提交哈希不同,也会得到相同的值。

$ git show <원본> | git patch-id --stable
37d09d56... fb42e70c...
$ git show <가져온 것> | git patch-id --stable
37d09d56... 075fc52e...

git cherry -v <upstream> <head>(占位符依次为上游与当前分支)会替你完成这个计算。开头的 - 表示该改动已经在 upstream 中,+ 表示还没有。

这里有一个重要的局限。解决冲突后带过来的提交,patch-id 会变。 因为上下文变了,diff 也就不同。所以 git cherry 会把那个提交标为 +(尚未存在)。如果完全相信自动判定而再次带入,同样的修复就会进来两次——这个工具回答的问题是“是否存在相同的 diff”,而不是“是否已经体现了相同的意图”。

在现场相遇的样子

下一项实验要做什么

先创建一个合并提交,在不带 -m 的情况下撤销它,看它被拒绝。用 -m 1 撤销之后,确认 git merge 输出 “Already up to date”,再通过 revert 的 revert 把它恢复。接着用 -x 带入热修复,其中一个会冲突的提交先用 --abort 退回去,再用 --continue 完成。最后用 git cherry 和 git patch-id 数一数哪些已经合入,就能亲眼看到为什么解决过冲突的提交仍保持 +。

官方文档是 git-revert、git-cherry-pick、git-cherry、git-patch-id。