不 checkout 更新 Git 分支:refspec 冒号的一次小技巧
PR 合并后本地 main 过期了,而你正站在下一个分支上。常见做法是 git switch main && git pull && git switch -——能用,但为了跑一次快进就 checkout 了 main,而 checkout 早就不便宜:开发服务器重启、watcher 重新触发、生成文件抖动,.gitignore 变过的话回来还要面对一堆 untracked 文件。两个上下文切换,只为了挪一个指针。
Git 本来就能移动你没站着的分支。
refspec 里的冒号
git fetch 接受 refspec:<source>:<destination>。source 是远程的 ref,destination 是这些历史落到你仓库里的位置。日常的 git fetch origin 的 refspec 来自配置 +refs/heads/*:refs/remotes/origin/*——远程分支进来,tracking ref 出去。手写一个 refspec,destination 可以是一个本地分支:
$ git branch --show-current
feature
$ git log --oneline -1 main
e7d9e76 init
$ git fetch origin main:main
From …/origin
e7d9e76..114c7a5 main -> main
e7d9e76..114c7a5 main -> origin/main
一次 fetch 更新两处:main:main 挪了本地分支,而因为 source 被配置的 refspec 覆盖,origin/main 也同步了。其他一切都没动——没有 checkout、没有重启、没有 index/working tree 变更,而 main 已是最新:git switch -c next main 从现在开始基于今天的提交,git rebase main 拿到新基线。
destination 不必已存在、也不必和 source 同名:
$ git fetch origin main:hotfix-base
* [new branch] main -> hotfix-base
一次 fetch 也可以带多个 refspec,让几个分支一趟全部更新。
远程是可选的
如果已经跑过 git fetch origin,新提交就在仓库里、只有 tracking ref 指着它们,本地分支是唯一还旧的东西——不需要第二次网络往返,仓库可以 fetch 自己:
$ git fetch origin
114c7a5..c21574c main -> origin/main
$ git fetch . origin/main:main
114c7a5..c21574c origin/main -> main
. 就是当前仓库,被当作自己的 remote。第一条命令走网络挪 tracking ref,第二条零网络把它拷到分支上。ref 更新本身完全一样,所以后续规则(拒绝、快进限制)对 . 的 fetch 同样适用。
你站着的分支是禁区
唯一例外是当前 checkout 的分支:
$ git fetch origin main:main
fatal: refusing to fetch into branch 'refs/heads/main' checked out at '…/repo'
注意是 fatal——整个 fetch 在传输任何数据前就中止,tracking ref 也不会更新。拒绝保护的是一个约定:checkout 是三件事的同步状态——分支 ref、index、working tree。fetch 只挪 refs、不做任何对账,瞄准已 checkout 的分支就会移动三者之一、让另外两个指向不再是 HEAD 的提交。
有个标志可以强制越过拒绝,正好演示为什么不该这么做:
$ git fetch --update-head-ok origin main:main # 仅演示,需要干净工作树
$ git status
Changes to be committed:
deleted: docs.md
那是一个你没做过的暂存删除——fetch 来的提交新增了 docs.md,而 index 还描述着之前的树,git status 把这报告成你的改动。从这里 commit 等于撤销你刚拉取的变更。工作树干净时 git reset --hard 能重建三者一致,但有未提交工作时它会连这些工作一起毁掉。
该标志的存在是因为 git pull 每次 fetch 都传它。普通 pull 不需要:fetch 半只写 origin/*,merge/rebase 半才动你的分支。但带 refspec 的 git pull origin main:main 确实会直接 fetch 进已 checkout 的分支——安全是因为 pull 紧接着就对账 index 和 working tree(输出里那句 "fast-forwarding your working tree"),这正是 --update-head-ok 单独使用时所缺的步骤。
同一拒绝覆盖任何地方 checkout 的分支:main 在 linked worktree 里时,从主 checkout fetch 进它同样失败,报错还会指名路径。这个拒绝也不是 fetch 的怪癖——git branch -f 对已 checkout 分支同样拒绝。
实践建议
- 想"看一眼新代码但不离开当前分支":
git fetch origin main:main是最短路径,无副作用; - 每次
git fetch origin之后git fetch . origin/main:main可以零网络补齐本地分支; - 别在带未提交工作的仓库里碰
--update-head-ok——它跳过的是 pull 里保命的对账步骤。
来源:Update a Git branch without checking it out - DEV Community