git status 里的 “ahead 2, behind 3” 是怎么算出来的?upstream 到底存在哪里?@{u} 和 @{push} 有什么不同?这篇造出一个分叉的场景,把这些全部拆开验证。
$ git config --get-regexp "^branch\." ; git branch -vv | sed -E 's/ [0-9a-f]{7} / <sha> /'
branch.main.remote origin
branch.main.merge refs/heads/main
* main <sha> [origin/main] m1分支的上游只是 config 里的两项:branch.<名>.remote 和 branch.<名>.merge。git branch -vv 把对应的本地缓存 origin/main 和领先落后数显示出来。
$ git status -sb | head -1; echo "(fetch 之前:不知道远程多了 3 个)"
## main...origin/main [ahead 2]
(fetch 之前:不知道远程多了 3 个)
$ git fetch -q; git status -sb | head -1; git status | sed -n 2,3p; git branch -vv | sed -E 's/ [0-9a-f]{7} / <sha> /'
## main...origin/main [ahead 2, behind 3]
Your branch and 'origin/main' have diverged,
and have 2 and 3 different commits each, respectively.
* main <sha> [origin/main: ahead 2, behind 3] A 本地 2ahead = 本地分支有而上游没有的提交数(upstream..branch),behind = 上游有而本地没有的提交数(branch..upstream)。两个数都不是 0 就是 diverged。
$ git log --oneline origin/main..main | sed -E "s/^[0-9a-f]+ //"; echo "-- 以上是领先的提交(只在本地)"; git log --oneline main..origin/main | sed -E "s/^[0-9a-f]+ //"; echo "-- 以上是落后的提交(只在远程)" A 本地 2 A 本地 1 -- 以上是领先的提交(只在本地) B 远程 3 B 远程 2 B 远程 1 -- 以上是落后的提交(只在远程) $ git rev-list --left-right --count main...origin/main; echo "(左边 = main 独有,右边 = origin/main 独有)"; git merge-base main origin/main | cut -c1-7 | sed "s/.*/merge-base = <sha>/"; git log -1 --format=%s $(git merge-base main origin/main) 2 3 (左边 = main 独有,右边 = origin/main 独有) merge-base = <sha> m1 不同的写法等价: $ git rev-list --count origin/main..main; git rev-list --count main..origin/main; git rev-list --count ^origin/main main; git rev-list --count main --not origin/main 2 3 2 2
数字用的是本地缓存的 origin/main,所以在 fetch 之前看不到远程新增的提交。
$ git rev-parse --abbrev-ref @{u}; git rev-parse --abbrev-ref @{upstream}; git rev-parse --abbrev-ref main@{u}; git rev-parse --abbrev-ref @{push}
origin/main
origin/main
origin/main
origin/main
$ git remote add fork /work/l29remote.git; git fetch -q fork; git config remote.pushDefault fork; git rev-parse --abbrev-ref @{push}; echo "-- 换成 push.default=current:"; git -c push.default=current rev-parse --abbrev-ref @{push}; echo "-- @{u} 不受影响:"; git rev-parse --abbrev-ref @{u}
-- 换成 push.default=current:
fork/main
-- @{u} 不受影响:
origin/main
fatal: cannot resolve 'simple' push to a single destination
$ git config --unset remote.pushDefault; git config branch.main.pushRemote fork; git -c push.default=current rev-parse --abbrev-ref @{push}; git config --unset branch.main.pushRemote; git remote remove fork
fork/maingit rev-list --left-right --count main...origin/main 给出 左边独有、右边独有 两个数;origin/main..main、^origin/main main、main --not origin/main 是等价的写法。
$ git switch -q -c plain; git config --get branch.plain.merge || echo "plain: 没有 upstream"
plain: 没有 upstream
$ git switch -q -c from-main main; git config --get branch.from-main.merge || echo "from-main(起点是本地分支): 没有 upstream"
from-main(起点是本地分支): 没有 upstream
$ git switch -q -c topic origin/topic 2>&1; git config --get branch.topic.remote; git config --get branch.topic.merge
origin
refs/heads/topic
$ git switch -q plain; git push -q -u origin plain 2>&1; git config --get branch.plain.remote; git config --get branch.plain.merge
origin
refs/heads/plain
$ git switch -q from-main; git branch -q --set-upstream-to=origin/main; git rev-parse --abbrev-ref from-main@{u}
origin/main
$ git config --get branch.autoSetupMerge || echo "branch.autoSetupMerge 未设置(默认 true:只有起点是 remote-tracking 分支才设置)"
branch.autoSetupMerge 未设置(默认 true:只有起点是 remote-tracking 分支才设置)@{u} 是 fetch / pull 的来源,@{push} 是 push 的去向,可以不同(例如 fork 工作流里从 origin 拉、往 fork 推)。push.default=simple 要求二者在同一个 remote,否则 @{push} 解析失败。
$ git switch -q topic; git fetch -q --prune; git branch -vv | grep topic | sed -E 's/ [0-9a-f]{7} / <sha> /'
* topic <sha> [origin/topic: gone] B 远程 3
$ git status | sed -n 1,3p
On branch topic
Your branch is based on 'origin/topic', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)只有新分支的起点是 remote-tracking 分支(git switch -c topic origin/topic)才会自动设置 upstream(branch.autoSetupMerge 默认 true);其它情况要 push -u 或 --set-upstream-to。上游被删之后显示 gone。