脚本先读取分支,再计算新提交,最后写回时,中间可能已经有别的任务改过同一分支。直接更新会把对方的结果盖掉。git update-ref 可以把旧提交一起传入,相当于给这次写入加一个版本检查。
环境: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 'Update Ref Demo'
git -C "$repo" config user.email '[email protected]'
printf 'base\n' >"$repo/app.txt"
git -C "$repo" add app.txt
git -C "$repo" commit -qm base
old=$(git -C "$repo" rev-parse HEAD)
git -C "$repo" branch deploy "$old"
# 任务 A 算出的候选提交
git -C "$repo" switch -qc candidate
printf 'candidate\n' >>"$repo/app.txt"
git -C "$repo" commit -qam candidate
candidate=$(git -C "$repo" rev-parse HEAD)
# 任务 B 抢先更新 deploy
git -C "$repo" switch -q main
printf 'concurrent\n' >>"$repo/app.txt"
git -C "$repo" commit -qam concurrent
concurrent=$(git -C "$repo" rev-parse HEAD)
git -C "$repo" update-ref refs/heads/deploy "$concurrent" "$old"
# 任务 A 仍拿旧值写入,应该失败
if git -C "$repo" update-ref refs/heads/deploy "$candidate" "$old"; then
printf 'unexpected update\n' >&2
exit 1
fi
test "$(git -C "$repo" rev-parse deploy)" = "$concurrent"
printf 'concurrent update preserved\n'
第三个参数是期望的当前值。只有 deploy 还指向 old 时更新才会成功;值已变化就返回非零,脚本可以重新读取并决定重算、重试还是退出。用于发布指针、缓存引用这类由多个任务竞争写入的场景,比“先比较、再单独写入”少一个竞态窗口。