bigywater.tech
Git · 32

远程跟踪分支 refs/remotes 的更新时机

origin/main 是远程的实时镜像吗?不是,它只是一个本地缓存,什么时候才更新?这篇用同一个远程的两个克隆(A 和 B)验证 push、fetch、ls-remote 各自的效果,以及 stale 分支、origin/HEAD、reflog 的行为。

4 个
git push、fetch、pull、clone 会更新 origin/*;commit、checkout、ls-remote 不会
stale
远程删了分支,本地的 origin/<分支> 仍在,直到 fetch --prune
不联网
git status 的 ahead/behind 只和本地缓存的 origin/main 比较

缓存的性质

实测 · 本机执行输出
  起点: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)才会更新它。

status 的盲点

实测 · 本机执行输出
  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。

push

实测 · 本机执行输出
  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)则一个字也不改。

stale 与 prune

实测 · 本机执行输出
  $ 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 才清理。

origin/HEAD

实测 · 本机执行输出
  $ 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。

reflog

实测 · 本机执行输出
  $ 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 的提交 2

remote-tracking ref 也有 reflog,origin/main@{1} 可以回看上一次 fetch 之前的位置。

分步演示

origin/* 的更新时机
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。A、B 是同一个本机裸仓库的两个克隆;为避免实时 hash 随运行变化,输出里的提交用“提交N”(第 N 个出现的 hash)代替;fetch.prune 在最后只做了设置,没有再验证其自动效果
    • 没有验证:git pull 的 fetch + merge/rebase 对 origin/* 的更新(与 fetch 相同);remote..fetch 自定义 refspec 时 origin/* 的映射(见上一篇 refspec);fetch.pruneTags;多个 remote 同名分支;通过网络协议(ssh / http)时的差异
    京ICP备2026067502号-1京公网安备11010502063487号