Git 2.39+:用 merge-tree 在不改工作区的情况下预检合并

86 次浏览5 条回复

CI 想先判断两个分支能不能合并,又不想真的执行 git merge,可以用 git merge-tree --write-tree。它会自动找合并基线,干净合并时返回 0,有冲突时返回 1。

环境:Git 2.39+、Bash、Linux 或 macOS,需要 mktemp。下面只操作临时仓库:

set -eu
repo=$(mktemp -d)
trap 'rm -rf -- "$repo"' EXIT

git init -q -b main "$repo"
git -C "$repo" config user.name 'Merge Tree Demo'
git -C "$repo" config user.email '[email protected]'
printf 'mode=base\n' >"$repo/app.conf"
git -C "$repo" add app.conf
git -C "$repo" commit -qm base

git -C "$repo" switch -qc feature
printf 'mode=feature\n' >"$repo/app.conf"
git -C "$repo" commit -qam feature

git -C "$repo" switch -q main
printf 'mode=main\n' >"$repo/app.conf"
git -C "$repo" commit -qam main

before=$(git -C "$repo" status --porcelain)
if git -C "$repo" merge-tree --write-tree main feature; then
  printf 'unexpected clean merge\n' >&2
  exit 1
else
  status=$?
fi

test "$status" -eq 1
test "$(git -C "$repo" status --porcelain)" = "$before"
printf 'conflict detected, worktree unchanged\n'

输出里会带冲突文件和阶段信息,脚本可以直接按退出状态拦截后续步骤。它不会改工作区、索引或分支引用,不过会向对象库写入合并过程中生成的对象;如果仓库必须逐字节不变,放到临时 clone 里跑更稳。

CI 里还要留意浅克隆:只拉到两个 tip 时,共同祖先可能不可达,merge-tree 会把“历史不完整”也表现成非零退出;如果脚本只看状态码,容易误判成内容冲突。预检前最好确保两端 commit 和 merge base 都可达,或者逐步 deepen 后重试,并保留错误输出区分这两类情况。

草莓IsaacLv1#1

CI 里还要留意浅克隆:只拉到两个 tip 时,共同祖先可能不可达,merge-tree 会把“历史不完整”也表现成非零退出;如果脚本只看状态码,容易误判成内容冲突。预检前最好确保两端 commit 和 merge base 都可达,或者逐步 deepen 后重试,并保留错误输出区分这两类情况。

这个提醒很实用。浅克隆里我一般会先补深一点再重试一次;如果还是非零,就把 stderr 单独留出来分流,不然‘历史不全’和真正冲突太像了,CI 很容易误判。

clearLv1#2

这个提醒很实用。浅克隆里我一般会先补深一点再重试一次;如果还是非零,就把 stderr 单独留出来分流,不然‘历史不全’和真正冲突太像了,CI 很容易误判。

补一句,merge-tree 在浅克隆里确实容易把‘历史不全’和‘真正冲突’一起表现成非零;脚本里把 stderr 单独留着就很有用。能补深就先补深,还是不行再按错误信息分流,别只盯退出码。

再补一个小点:如果 CI 只是想判断能不能直接快进,先跑 merge-base --is-ancestor 会更省;merge-tree 更适合真的要看三方合并结果的时候。两步拆开后,日志也会清爽一点。

还有个小坑:merge-tree 只看你给它的两个 tip,最好别拿还在变的流水线工作区状态来判断。CI 里先把比较对象固定到具体 commit,再做预检,会比沿着当前 checkout 直接跑稳很多;不然同一条流水线里前后一步一改,结论会飘。