本节摘要:Git 分支不是目录的拷贝,而是一个指向提交的轻量级指针——创建分支只需写一个 40 字符的文件。HEAD 是"指向指针的指针",决定当前你在哪个分支工作。正是这种轻量,让并行开发、功能隔离、实验试错成为 Git 的日常。本节用多张指针图把这两个概念讲透,并给出五种典型用途。
阅读完本节,你应当能够:
先问一个反直觉的问题:Git 里创建一个分支要花多长时间?
如果你从 SVN 时代走过来,会觉得"建分支=复制整个项目目录",大项目要几分钟。如果你习惯了 Git,会觉得"建分支=写一个小文件",毫秒级完成。同一个词"分支",两种系统背后是完全不同的物理动作——这正是理解 Git 分支的第一把钥匙。
再想象一个场景:你正在给项目加一个"导出 PDF"的功能,改到一半,老板说线上有个 bug 得立刻修。你不想让修 bug 的改动和做功能的半成品搅在一起。怎么办?
没有分支,你只能祈祷、或者把半成品藏起来。有分支,你花一秒开一条 bugfix 线,切过去修 bug,修完切回来继续做功能,两条线互不干扰。这个"一秒切线"的能力,就是 Git 分支轻量带来的自由。
Git 的分支极其朴素:它只是一个指向某个提交的指针。物理上,每个分支就是 .git/refs/heads 目录下的一个小文件,文件内容是一串 40 字符的提交哈希。
比如 main 分支指向提交 C3,那么 refs/heads/main 文件里就写着 C3 的哈希。你每提交一次,所在分支的指针就自动前移一次,指向最新的提交。
main 是指向提交的指针,HEAD 是指向 main 的指针。这个"双层指针"结构,是理解 Git 一切分支行为的地基。
创建分支 git branch feature 时,Git 只是新建一个指针文件,让它指向当前提交。整个过程不复制任何文件、不动任何历史,所以几乎瞬时完成——无论你的仓库有多大。
HEAD 是一个特殊指针,指向"你当前所在的分支"。执行 git switch 时,Git 做两件事:把 HEAD 指向新分支,把工作区更新成新分支指向的提交的内容。
看一个逐步演进的例子。你在 main 上做了两次提交,然后创建并切换到 feature 分支:
# 状态:HEAD → main → C3 git branch feature # feature 指针也指向 C3,HEAD 仍指向 main git switch feature # HEAD 改为指向 feature,工作区内容不变(因为指向同一提交)
在 feature 上提交一次 C4 后:
注意看:只有 feature 的指针前移到了 C4,main 还停在 C3。这就是分支实现隔离的关键——每个分支的指针独立前进,互不干扰。

分支的"便宜"带来三个连锁效应:
效应一:频繁开分支没压力。 每个小任务开一条分支,主线始终干净,随时可发布。
效应二:实验成本趋近于零。 想试个新方案?开条实验分支,试坏了直接删,主线毫发无损。
效应三:团队并行成为日常。 A 在 feature-A 开发,B 在 feature-B 开发,谁都不挡谁,最后各自合并回主线。
这三点在 SVN 时代几乎做不到(分支太贵),是 Git 的分支设计把"并行开发"从口号变成了默认实践。
| 用途 | 场景 | 分支名建议 |
|---|---|---|
| 并行开发 | 多成员/多任务同时进行 | feature/xxx |
| 功能隔离 | 新功能在独立线上做,不影响主线 | feature/user-login |
| 实验探索 | 试新方案,失败就丢 | experiment/xxx |
| Bug 修复 | 紧急修线上问题,不打断主线 | hotfix/xxx |
| 版本发布 | 发布前冻结、稳定分支 | release/1.0 |
分支名用斜杠分层(feature/xxx)是约定俗成,便于分类管理,不是语法要求。
把抽象作用还原成场景:一个小团队三个人,产品要求本周同时交付三个功能。
一天下来,三人的提交分别落在三条链上,互不相碰。下班前各自把完成的部分合并回 main——可能撞出冲突(3.4 节处理),但大部分时候顺顺当当。对比没有分支的版本:三个人挤在一条主干上,改同一个文件时只能排队,改错一处全线遭殃。这就是"分支轻量"的真实分量——它把一个团队的协作模式从"串行排队"变成了"并行流水线"。
从第 2 章到这一章,你的心智模型要升级一次:之前你操作的是"文件在三区之间流动",现在你要习惯"提交构成一条链,分支是在链上移动的指针"。以后看任何 Git 问题,先问"这条链长什么样、指针指向哪",再看文件状态。
这个心智切换不是一天完成的,但有几个练习能加速:
git log --oneline --graph --all,看提交链的形状。坚持一两周后,你会发现自己看 Git 的方式变了:不再逐个记命令,而是看到"指针在动"。一旦到了这个境界,后面学 merge、rebase、远程同步都会快得多,因为它们本质上都是"指针在不同场景下的运动"。
git switch --detach 提交哈希 会让 HEAD 直接指向某个提交而不是分支,进入"分离 HEAD"状态。此时提交的新提交不属于任何分支,一旦切走就可能"丢失"(只能靠哈希或 reflog 找)。
分离 HEAD 本身不危险,危险的是在里面提交完又切走。想保留分离状态的改动,先
git switch -c 新分支名把它变成真分支。
光说"分支是一个文件"不够,亲手验证最实在。在练习仓库里执行:
git branch feature-test cat .git/refs/heads/feature-test # 显示一串哈希 cat .git/refs/heads/main # 和 main 指向同一提交(刚创建时) cat .git/HEAD # 显示 ref: refs/heads/当前分支
三行输出就把"分支=指针文件、HEAD=指向分支的指针"钉死了。接下来在 feature-test 上提交一次,再 cat 一遍 feature-test,会看到哈希变了、main 的没变——这就是指针独立前进的直接证据。这个"看文件"的小动作,比读十遍理论都管用,建议每个人都亲手做一次。
新手常困惑:两个分支明明开始指向同一提交,怎么后来内容就不一样了?答案藏在前面的指针图里——内容差异不是"复制"出来的,是"分别提交"长出来的。main 和 feature 最初指向同一提交,之后你在 feature 上提交 C4,main 上提交 C5,两个分支的提交链从共同祖先处分叉,各自的内容自然就不同了。Git 的合并(下一节)就是"把分叉的链重新接上"。
理解这一点,"分支之间差了什么"这个问题就有了精确答案:看它们各自的提交链在共同祖先之后多了哪些提交。后面讲 merge 的三方合并时,这个"共同祖先"会成为关键角色。
⚠️ 常见坑:在错误的分支上提交了改动。切分支前先
git status确认当前分支,这是"改错分支"事故的唯一解药。
💡 关键直觉:把分支想成"书架上的标签"。同一个提交可以贴多个标签,标签可以随时移动、随时撕掉重贴。文件是书,分支是标签——书只有一套,标签想要多少有多少。
"分支会占很多磁盘空间吗?" 不会。分支只是 40 字符的指针文件,真正的数据是提交对象,多个分支共享。即使开一百个分支,磁盘占用也几乎无感。
"为什么切分支有时很快、有时要等一下?" 切换本身是秒级的(改指针+更新工作区);如果两个分支内容差异很大,更新工作区文件会稍久,但仍是本地操作,远快于网络传输。
"能同时看到两个分支吗?" 工作区一次只能处于一个分支(除非用 worktree 高级功能,本书不展开)。但你可以随时切来切去,指针决定你看哪条线。
"删掉分支会删掉提交吗?" 不一定。如果提交仍被其他分支/标签引用,删除分支只是少了一个入口,提交对象还在。只有完全没人引用的提交才可能被垃圾回收。所以"删分支=丢代码"是误解,真正丢代码的是 reset --hard 未提交的改动。
"为什么大家都在强调分支要短命?" 分支越长寿,和主干的差异越大,合并时冲突越多、代码审查越难。推荐的节奏是"小而短":功能分支持续几天到一两周,做完立刻合并、删除。那种挂了几月没合并的"僵尸分支",要么是功能烂尾了,要么是团队没养成清理习惯——两者都该避免。
"分支可以嵌套吗?feature/a 能不能挂到 feature/b 下面?" 分支本身是平级的指针,没有物理嵌套。名字里的斜杠(feature/a)只是分类习惯,不代表层级。想表达"基于某个分支开的分支",创建时指定基点即可:git switch -c feature/b feature/a。
"我想看看某个分支现在长什么样,但不想切过去,行吗?" 行。用 git log 分支名 --oneline 直接看那个分支的提交历史,git show 分支名:文件路径 看它在某个文件里的内容,git diff main..feature 看两个分支的差异。这些都是只读操作,不改变当前分支,适合"侦查"。
下一节动手操作:创建、切换、删除分支——把指针模型落到命令上。