git log --cherry-pick A..B 实际排除 A 中已存在的等价提交,仅显示 B 中独有的提交;真正求共有提交需用交集逻辑,如 git log --oneline --ancestry-path $(git merge-base A B)^..A --not $(git merge-base A B)^..B 或配置别名 common 命令。

git log --cherry-pick 为什么不能直接看到“共有提交”
很多人以为 git log --cherry-pick A..B 能列出 A 和 B 的共有提交,其实它干的是相反的事:它排除掉在 A 中已“等价存在”的提交(即补丁 ID 相同),只显示 B 中独有的。真正想找“两个分支都包含的提交”,得换思路——不是用差集,而是求交集。
Git 本身没内置 git common-commits A B 这种命令,但可以用底层机制组合出来:
-
git merge-base --all A B只返回最近公共祖先(LCA),对多分叉、重基后场景不全 - 更可靠的方式是:先拿到 A 上所有提交,再筛选出也在 B 的可达集合里的那些
- 本质是
git rev-list A --not $(git rev-list --no-merges ^B)的逆向思维,但太绕,不推荐手敲
用 git log + --ancestry-path 求交集最稳
实际验证过最简洁可靠的方案是:
git log --oneline --ancestry-path $(git merge-base A B)^..A --not $(git merge-base A B)^..B
但这还是反直觉。更推荐这个别名(加到 ~/.gitconfig):
[alias]
common = "!f() { git log --oneline --cherry-mark --left-right \"$1...$2\" | grep '^<' | sed 's/^< //'; }; f"
说明:
-
git log --left-right \"$1...$2\"会把左边分支($1)的提交标为,右边(<code>$2)标为> -
--cherry-mark自动跳过补丁等价但 SHA 不同的重复提交(比如 rebase 后) grep '^ 筛出只属于 <code>$1的?不对——等等,这里关键点来了:在A...B对称差集中,**同时出现在 A 和 B 中的提交根本不会被列出**,所以这法子也不行
绕回正解:唯一能保证不漏的,是分别获取两分支的提交集,再取交集:
使用四维度框架评估任意 GitLab MR 或 GitHub PR 的复杂度:规模(20%),认知负荷(30%),审查工作量(30%),风险/影响(20%)...
[alias]
common = "!f() { comm -12 <(git rev-list --first-parent \"$1\" | sort) <(git rev-list --first-parent \"$2\" | sort) | head -20 | xargs -r git log --oneline --no-walk; }; f"
注意点:
-
--first-parent避免 merge 提交带入无关主干历史,适合看“主线共有” -
comm -12要求输入已排序,所以必须sort;zsh 用户可能需加set +o multios防止重定向报错 - 如果某分支有大量提交,
git rev-list可能慢,可加--max-count=500限制范围
别名里怎么处理带空格或特殊字符的分支名
直接传参如 feature/login-flow 没问题,但像 origin/HEAD 或含 * 的 glob 表达式会崩。安全做法是让 shell 层先做引用展开:
[alias]
common = "!f() { a=$(git rev-parse --verify \"$1\") && b=$(git rev-parse --verify \"$2\") && comm -12 <(git rev-list --first-parent \"$a\" | sort) <(git rev-list --first-parent \"$b\" | sort) | head -20 | xargs -r git log --oneline --no-walk; }; f"
这样 git common HEAD origin/main、git common v1.2.0 'refs/tags/v1.2.*'(需额外支持 glob)都能兜住。不过 git rev-parse --verify 对不存在的引用会失败,建议加 || { echo \"Invalid ref: $1 or $2\" >&2; exit 1; }。
为什么不用 git show-branch -a
git show-branch -a 看起来很接近需求,但它只是可视化展示各分支的提交分布,**不输出纯提交哈希列表**,也没法管道过滤。想自动化处理(比如统计数量、查某次提交是否共存),它就卡住了。而且默认只显示最近 10 层,深层共有提交容易被截断。
真正要写 CI 脚本或集成进 IDE 插件时,还是得靠 rev-list + comm 这套组合——虽然多敲几个字,但逻辑清晰、结果确定、无歧义。
最常被忽略的一点:如果两个分支从不同初始提交开始(比如一个 fork 自另一个仓库),git merge-base 可能为空,此时 rev-list 方案仍有效,而依赖 LCA 的所有方法都会失效。

















