origin/main 是远程的实时镜像吗?不是,它只是一个本地缓存,什么时候才更新?这篇用同一个远程的两个克隆(A 和 B)验证 push、fetch、ls-remote 各自的效果,以及 stale 分支、origin/HEAD、reflog 的行为。
起点:A 的 origin/main = 提交1;B 的 origin/main = 提交1 A: git commit(本地提交) origin/main: 提交1 → 提交1 没变 A: git ls-remote origin origin/main: 提交1 → 提交1 没变 A: git checkout -b x; git checkout main origin/main: 提交1 → 提交1 没变 A: git push origin main origin/main: 提交1 → 提交2 变了
origin/main 是本地保存的“上次和远程通信时,远程的 main 在哪里”,不是实时镜像。只有真正和远程通信并成功的命令(fetch、pull、push、clone)才会更新它。
B 的缓存:
B 的 origin/main = 提交1(远程的 main 是 提交2)
$ git status -sb | head -1; echo "(B 的 git status 仍然说 up to date,因为它只和自己缓存的 origin/main 比较)"
## main...origin/main
(B 的 git status 仍然说 up to date,因为它只和自己缓存的 origin/main 比较)
B: git fetch origin/main: 提交1 → 提交2 变了
$ git status -sb | head -1
## main...origin/main [behind 1]git status 的落后 / 领先提示只拿本地的 origin/main 比较,不联网,所以别人推了新提交之后你的 status 仍说“up to date”,直到下一次 fetch。
B 推送(远程已有 A 的提交 3,B 没有): $ git push origin main 2>&1 | grep -E 'rejected|error' ! [rejected] main -> main (fetch first) error: failed to push some refs to '/work/l28remote.git' hint: Updates were rejected because the remote contains work that you do not B 的 origin/main 推送前后: 提交2 → 提交2(没变:push 被拒绝,不会更新) $ git push -q origin main:refs/heads/b-copy; git for-each-ref refs/remotes --format="%(refname:short)" | tr "\n" " "; echo origin origin/b-copy origin/main
push 成功会同时更新对应的 origin/<名字>;被拒绝(例如 fetch first)则一个字也不改。
$ git fetch -q; git branch -r | tr "\n" " "; echo; git remote show origin 2>&1 | grep -E "stale|tracked" | head -3
origin/HEAD -> origin/main origin/b-copy origin/main
main tracked
refs/remotes/origin/b-copy stale (use 'git remote prune' to remove)
$ git fetch --prune 2>&1 | sed -E "s#From .*#From <远程>#"; git branch -r | tr "\n" " "; echo
From <远程>
- [deleted] (none) -> origin/b-copy
origin/HEAD -> origin/main origin/main
$ git config fetch.prune true; git config --get fetch.prune
true远程删除分支后,本地的 origin/<分支> 不会自己消失,git remote show origin 标成 stale;git fetch --prune 或配置 fetch.prune=true 才清理。
$ cat .git/refs/remotes/origin/HEAD; git symbolic-ref refs/remotes/origin/HEAD ref: refs/remotes/origin/main refs/remotes/origin/main $ git fetch -q; git symbolic-ref refs/remotes/origin/HEAD; echo "(fetch 之后仍是 main)"; git remote set-head origin -a; git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main (fetch 之后仍是 main) 'origin/HEAD' has changed from 'main' and now points to 'trunk' refs/remotes/origin/trunk
refs/remotes/origin/HEAD 在 clone 时设置,指向远程当时的默认分支;远程改了默认分支后,fetch 不会更新它,要 git remote set-head origin -a。
$ git reflog show origin/main --format='%gd: %gs' | head -6
origin/main@{0}: fetch -q: fast-forward
origin/main@{1}: fetch -q: fast-forward
$ git log -1 --format=%s 'origin/main@{1}'
A 的提交 2remote-tracking ref 也有 reflog,origin/main@{1} 可以回看上一次 fetch 之前的位置。