cherry-pick 摘过去的提交和原来的是同一个提交吗?git 怎么知道两个提交是“同一个补丁”?这篇验证 cherry-pick 的三方合并本质,算 patch-id,再看 git cherry 和 log --cherry-pick 依据什么判断,以及哪些情况会让 patch-id 失效。
$ git cherry-pick -x 595215f35d377425cb3635144db29426ffa25682 2>&1 | head -2
Auto-merging f.txt
[main 2128741] 修复:第 10 行
$ git log -1 --format='%s%n author=%an committer=%cn%n parent: %p' | sed -E 's/[0-9a-f]{7}/<sha>/'; git log -1 --format=%b | sed -E 's/[0-9a-f]{40}/<sha40>/'
修复:第 10 行
author=Alice committer=Bob
parent: <sha>
(cherry picked from commit <sha40>)
原提交 parent 是 基础提交,新提交 parent 是 main 的上一个提交;两个提交的 tree 不同(新的带有 main 加的两行): True把一个提交的改动在当前分支上再做一遍:新提交保留原作者和说明,提交者换成执行者,parent 不同,hash 当然不同;-x 在说明里记下来源。
merge-tree 算出的 tree: 8d786e2a105f cherry-pick 之后 HEAD 的 tree: 8d786e2a105f 相同: True
基础是那个提交的 parent,ours 是当前 HEAD,theirs 是那个提交,结果 tree 配上原作者和说明做成新提交。用 git merge-tree 手工做同样的合并,tree 与 cherry-pick 的结果完全一致。
原提交的 patch-id : b2ad9fde2a9c9a6afc8e26dd60cd97206555b913
新提交的 patch-id : b2ad9fde2a9c9a6afc8e26dd60cd97206555b913
相同: True(hash 不同: True;行号不同: 原来改的是第 10 行,新提交里变成第 12 行)
$ git show HEAD | grep "^@@"; git show 595215f35d377425cb3635144db29426ffa25682 | grep "^@@"
@@ -9,7 +9,7 @@
@@ -7,7 +7,7 @@
对照:补丁里多改一个字,patch-id 就不同;只改空白,默认的 --stable 会忽略空白:
A(改第 1 行) 5e3837421ef7 | 同样内容但行号是 10 5e3837421ef7 | 多一个感叹号 4c68380e8a0e | 行尾多空白 5e3837421ef7git patch-id 对补丁的增删内容取指纹,忽略行号;--stable 还忽略空白。所以同一个补丁摘到文件的不同位置仍有相同的 patch-id,改动内容一变就不同。
$ git cherry -v main feature | sed -E "s/[0-9a-f]{40}/<sha40>/"
+ <sha40> 修复:第 10 行
+ <sha40> 后续提交
$ git cherry -v main feature | sed -E "s/[0-9a-f]{40}/<sha40>/"; echo "(- 表示 main 里已经有等价的补丁,+ 表示还没有)"
- <sha40> 修复:第 10 行
+ <sha40> 后续提交
(- 表示 main 里已经有等价的补丁,+ 表示还没有)
$ git log --left-right --cherry-pick --oneline main...feature | sed -E "s/[0-9a-f]{7} //"; echo "(--cherry-pick 把互相等价的提交过滤掉了)"
< main 在文件开头加两行
> 后续提交
(--cherry-pick 把互相等价的提交过滤掉了)git cherry、git log --cherry-pick、rebase 跳过重复补丁都用 patch-id:相同则认为“上游已经有这个改动”。
$ git cherry-pick f4ff8a4be3ff7a9b1bb5c1c938fe7b39a89ddfd1 2>&1 | head -2; ls .git | grep CHERRY_PICK_HEAD
Auto-merging f.txt
CONFLICT (content): Merge conflict in f.txt
CHERRY_PICK_HEAD
原提交 patch-id e24b90ae0805070fda29f8fa6906dd632636dab4 ;解决冲突后的提交 patch-id 45ea0bf645fd8a0595247bb4a0cbdb9d206b1094;相同: False
$ git cherry -v main feature | sed -E "s/[0-9a-f]{40}/<sha40>/"
+ <sha40> 改第 10 行冲突后手工合并的结果往往不再是原来的补丁,patch-id 随之改变,上面这些命令就认不出它们是同一个改动;所以 -x 记下来源很有用。
$ git cherry-pick 595215f35d377425cb3635144db29426ffa25682 2>&1 | head -3 Auto-merging f.txt The previous cherry-pick is now empty, possibly due to conflict resolution. If you wish to commit it anyway, use:
补丁已经在当前分支里时,cherry-pick 的结果为空,git 会停下来让你选择跳过或保留空提交。