Git first-parent:日志只沿主线走,合入的功能提交去了哪里

前天 3阅读

查看一次集成分支的演进,日志里却挤满功能分支的中间提交:改名一次、补测试一次、修拼写一次。想把视角移到“主线何时接纳了一组变化”,可以使用 first-parent。它改变沿提交图回溯的路径,并不会删掉那些功能提交。

合并提交可以有多个父提交。常见的双亲合并中,第一个父提交是执行合并时所在分支原来的头,第二个父提交是合入的另一端。first-parent 遇到合并后只沿第一条父边继续,所以它能保留集成节点,并略过旁支里的逐步实现。

造一个有两条发展路线的小仓库

保存为 demo.sh,用 bash demo.sh 运行。本文用 Git 2.52.0 与 Bash 5.2.37 实跑。脚本创建并清理自己的临时目录,身份和签名配置只落在示例仓库。没有远端,也不会改动已有项目。

Git first-parent:日志只沿主线走,合入的功能提交去了哪里

AI概念插图:用抽象图形表现本文讨论的关系,并非软件截图或真实运行结果。

#!/usr/bin/env bash
set -euo pipefail
demo_dir=$(mktemp -d)
trap 'rm -rf -- "$demo_dir"' EXIT
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_DATE='2026-01-01T00:00:00Z'
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git init -q -b main "$demo_dir"
cd "$demo_dir"
git config user.name Demo
git config user.email demo@example.invalid
git config commit.gpgsign false
printf 'base\n' > base.txt
git add base.txt
git commit -qm base
git switch -qc feature
printf 'one\n' > feature.txt
git add feature.txt
git commit -qm feature-one
printf 'two\n' >> feature.txt
git commit -qam feature-two
git switch -q main
printf 'main\n' > main.txt
git add main.txt
git commit -qm main-work
git merge -q --no-ff feature -m merge-feature
all_count=$(git rev-list --count HEAD)
first_count=$(git rev-list --first-parent --count HEAD)
plain_count=$(git rev-list --no-merges --count HEAD)
[[ $all_count == 5 && $first_count == 3 && $plain_count == 4 ]]
printf 'all=%s first-parent=%s no-merges=%s\n' "$all_count" "$first_count" "$plain_count"
git log --first-parent --reverse --format='%s' HEAD
[[ $(git show HEAD^1:main.txt) == main ]]
[[ $(git show HEAD^2:feature.txt) == $'one\ntwo' ]]
[[ $(git show HEAD:feature.txt) == $'one\ntwo' ]]
printf 'both parents checked; feature content remains\n'

三种数量来自三种遍历规则

输出的提交数量分别是五、三、四。完整历史包含 base、两次 feature 修改、main-work 与 merge-feature。first-parent 的三条标题则依次是 base、main-work、merge-feature。这就是主线在本例中的演进轮廓。

no-merges 返回四,并没有得到同样的主线视角。它只是去掉合并提交,旁支中的 feature-one 和 feature-two 仍然可以出现。一个选项控制回溯时走哪条父边,另一个过滤合并节点;两者不能根据“日志看起来短了”就互相替换。

末尾分别读取 HEAD 的第一父、第二父以及合并结果。第一父有主线文件,第二父有功能文件,合并结果也保留功能内容。这些断言说明:展示旁支提交的方式变了,最终文件内容没有因此丢失。

第一父来自合并方向,不来自分支名字

Git 不会因为某条分支名叫 main 就自动把它认作第一父。如果当时在 feature 上执行合入 main,再把指针安排到其他名字,第一父关系仍由原合并对象记录。阅读主线历史前,要先确认团队的合并方向和实际提交图。

示例使用 no-ff,并且让主线与功能分支各自产生修改,确保生成双亲合并节点。若采用快进,只移动分支指针,并不会凭空产生这样一个集成节点。若团队习惯压成单提交,日志粒度又会不同,应依据工作流解释结果。

first-parent 也不等于只显示合并。main-work 是普通提交,它仍留在结果中。想写发布摘要,可以先沿这条路线寻找集成位置,再进入某次合并检查具体差异。把这份简略清单当成全部开发活动,会遗漏旁支上的细节。

本实验没有添加文件路径筛选,也不讨论更复杂的历史简化规则。实际排查某个文件时,路径限制可能进一步改变显示结果。若数量与预期不同,先用没有路径条件的图形日志核对父边,再逐个加入过滤条件。

把问题表述清楚后,命令就更容易选:需要集成节奏,看第一父路径;需要完整可达提交,看全图;需要非合并实现提交,再用对应过滤。一次只改变一个条件,才能知道日志少掉的究竟是哪一类信息。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。