本节摘要:分支是
refs/heads下一个只含 40 位哈希加换行的文本文件,HEAD 是"引用的引用"。本节直接打开这些文件取证,讲清分支创建与切换的成本模型、detached HEAD 的判定与风险,以及branch/switch/checkout三条命令的关系。
读到这里请先停一下,问自己一个问题:在你的想象里,"创建一个分支"时计算机到底做了什么?复制整个项目?在服务器上登记一个名字?还是仅仅在本地写下一行文字?带着你的猜想往下读,答案会在第一节揭晓,而你猜得对不对,直接决定了你对 Git 的理解处于哪一层。本节结束时你应当能够:
接着第 1 章的实验仓库,现在只有一条分支。看证据:
cat .git/HEAD # ref: refs/heads/master cat .git/heads/master 2>/dev/null; cat .git/refs/heads/master # a1b2c3d4e5f6...(40位哈希 + 换行)
这就是分支的全部真身。refs/heads/master 是一个 41 字节(40 字符加换行)的文本文件,内容是它指向的提交哈希。所谓的"在 master 上开发",等价于"HEAD 附着在 refs/heads/master 上,每次提交把该文件里的哈希换成新提交的哈希"。创建分支为什么快得像什么都没发生?因为它真的只是新建一个小文件:
git branch dev # 新建 .git/refs/heads/dev,内容与 master 相同 git rev-parse master dev # 两个引用解析到同一个哈希
两个引用此刻指向同一提交。git log --graph --oneline --all 里它们像两根线叠在一起,其实根本没动任何对象。对比一下 SVN:它的分支是服务端目录复制,代价以秒计;Git 分支纳秒级,这是第 5 章各种"多分支工作流"能成立的物理前提。
需要说明的是,引用文件并不总是落在 refs/heads 的散装文件里——活跃仓库会把这些文件打包进 .git/packed-refs 提高效率,但语义完全一致,git rev-parse 与 git show-ref 都能读:
git show-ref # a1b2c3d... refs/heads/master # a1b2c3d... refs/heads/dev
.git/HEAD 的内容通常是 ref: refs/heads/某分支——HEAD 不直接记哈希,而是记"我附着在哪个引用上"。这个间接层让提交时的指针移动成为可能:
git switch dev printf "dev work\n" > dev.txt git add dev.txt && git commit -m "dev commit" cat .git/refs/heads/dev # 哈希已更新为新提交 cat .git/refs/heads/master # 哈希纹丝未动
提交的内部动作序列值得背下来:生成 blob 与 tree、生成 commit(parent 是 dev 原来指向的提交)、把 HEAD 附着的那个引用文件里的哈希改写为新提交。HEAD 自身的文件内容一个字节都没变,变的是它指向的引用。而 git reflog 记录的是 HEAD(及各引用)每次移动的前后哈希——这就是第 3 章要用的"监控录像"。
当你 git checkout <某个哈希或标签> 时,HEAD 不再附着于任何分支引用,而是直接记一个哈希:
git checkout HEAD~1 # You are in 'detached HEAD' state. cat .git/HEAD # a1b2c3d4...(直接是哈希,没有 ref: 前缀)
此状态下提交,新提交不属于任何分支——HEAD 挪走了,但没有任何 refs/heads 文件指向它。一旦你 checkout 离开,这些提交就成了"悬空证物",最终被垃圾回收清除。detached HEAD 用来看历史非常安全(只读勘查),用来提交则要小心:要么先建分支再动手(git switch -c fix-now),要么接受这是临时快照。清理工作现场或 CI 构建正是利用 detached HEAD"可提交但零污染"的特性。
git checkout 是条老命令,一人分饰两角:切分支 + 恢复工作区文件。git checkout -- f.txt 恢复文件、git checkout dev 切换分支,语义靠参数区分,误操作风险高(打错一个词就可能覆盖工作区)。Git 2.23 起拆成两条:git switch 只管切分支,git restore 只管恢复文件。老教程里 checkout 满天飞,新代码库里建议团队统一用 switch/restore,可读性和安全性都更好。
切换分支时 Git 做的检查也值得知道:它比对"工作区未提交的改动"与"目标分支的内容",若改动会覆盖即拒绝切换并提示先提交或暂存。理解了第 1 章的三区模型,这个报错就不再是玄学——Git 只是拒绝一次会销毁证据的回填。
💡 关键直觉:把引用想成书签,把 HEAD 想成"你手指 currently 停在哪"。书签可以随便夹(建分支零成本),手指也可以指向任何一页(detached),但翻页时(提交)只有手指附着的书签会被挪动。如果你在一页没有书签的纸上写字(detached 提交),合上书后很难再翻回那页。
下一节看两根指针指向不同提交时如何汇合——合并的两种形态与冲突现场勘查。