分支与 HEAD
Git 分支是指向一个提交的可移动名称。HEAD 通常保存当前检出分支的名称,下一次提交只推进该分支。分离 HEAD 则省去分支这一层,直接指向提交;新提交会移动 HEAD,不移动分支引用。
创建分支不复制文件、提交或历史,只写入另一个保存相同提交 ID 的名称。后续提交移动某个名称,历史才开始分叉。
分支和 HEAD 如何工作
- 提交对象不可变,包含树、父提交信息、身份、消息等字节,对象 ID 由这些字节计算。
- 分支引用如
refs/heads/main保存一个提交 ID,分支前进时替换它。 - HEAD 通常是符号引用,保存当前分支名;分离状态则直接保存提交 ID。
附着状态的解析链有两跳:HEAD → refs/heads/main → 6f09690。提交先沿链找到父提交,写入新的不可变对象,然后更新 main。动画把对象写入与引用更新拆开,因此能看到对象已经存在、却还没有分支指向它的瞬间。
git branch feature 解析当前 HEAD,把同一个 ID 写入 refs/heads/feature,不会切换 HEAD。若 main 已指向 6f09690,此时两个名称指向同一个对象。
git switch feature 把符号 HEAD 改为 refs/heads/feature,不移动任一分支末端。真实仓库还可能同步索引与工作区;本课所有提交使用固定空树,只观察引用变化。
分支与 HEAD 逐步演示
默认执行六条命令:
git commit --allow-empty -m "base"
git branch feature
git switch feature
git commit --allow-empty -m "feature-1"
git switch main
git commit --allow-empty -m "main-1"
初始分支尚未诞生:HEAD 已命名 main,但 refs/heads/main 尚无目标提交。
- 首次提交没有父对象,先写入根提交
6f09690...,再让 main 从未诞生状态指向它。 git branch feature写入第二个引用6f09690...。图中仍只有一个提交,HEAD 仍命名 main。git switch feature只把 HEAD 的名称由 main 改为 feature,两分支仍在同一根提交。- 提交
feature-1解析HEAD → feature → 6f09690...,写入子提交4395860...,再只推进 feature。main 留在基点。 - 切回 main,HEAD 改为 main 引用,不会把 main 拖到 feature 的提交,它仍保存
6f09690...。 - 提交
main-1也以该基点为父,写入1dc9ff9...,只推进 main。最终是共同根和两个子提交,两个分支由各自提交推进而分叉。
播放器显示唯一的短 ID 前缀,引擎保存完整 40 字符 SHA-1 格式 ID。它与提交快照课程使用相同的固定身份、空树和提交序列化方式;提交时间为 1700000000 加该提交的序号(第一个提交为 0),所以每个提交的时间戳都不同。
分离 HEAD 会怎样
分离时 HEAD 直接指向已存储提交。仍然可以提交:以 HEAD 的 ID 为父,创建子提交,再更新 HEAD。没有选中分支,因此没有分支移动。这适合在指定提交上检查或实验,但要留意可达性。
分离后失去引用预设在 6f09690... 分离,创建 temp,再切回 main。临时提交还在对象库中,只是没有分支能到达它。画面标为未引用,而非已删除。真实 Git 通常在 reflog 保存近期 HEAD 位置,一段时间内仍可找回;reflog 过期和对象清理是不同机制。
挽救分离提交预设在切走前加上:
git branch rescue
git switch rescue
第一条把 refs/heads/rescue 指向分离提交,第二条使 HEAD 再次成为符号引用。提交图没变,但有一个持久名称能到达该提交。
边界情况
- 尚未诞生的 HEAD: 没有提交时
HEAD → main也合法。首次提交创建根对象并写入 main 的首个 ID。本引擎此时拒绝创建 feature,因为没有有效目标可复制。 - 分支重名: feature 已存在时创建失败,不覆盖引用,也不增加第二个标签。
- 未知分支:
git switch missing失败,HEAD、引用、对象都不变。 - 未知提交: 本引擎分离操作要求完整 ID 且对象存在,否则 HEAD 不变。
- 多个引用指向同一提交: 正常状态,不是合并;各名称独立保存相同 ID。
- 分离时创建分支: 新分支得到 HEAD 当前 ID,但只有 switch 后 HEAD 才重新附着。
- 短 ID 碰撞: 从七字符开始,必要时加长前缀;完整 ID 才是对象身份。
- 显示限制: 最多四个分支、十个提交、二十条命令,是可视化限制,不是 Git 限制。
常见错误
- “分支装着所有提交。” 分支只保存一个 ID,沿父链接才能找到历史。
- “创建分支复制历史。” 它只增加引用,同一末端后面的对象共享。
- “HEAD 就是最新提交。” 它记录当前选择,可以解析到旧分支末端,也可以分离指向旧提交。
- “切分支会移动该分支。” 切换移动的是 HEAD。若改写目标分支,就会丢掉本来要访问的末端位置。
- “父提交上的所有分支都会随提交前进。” 只有符号 HEAD 指定的分支前进,否则独立开发线就会合并在一起。
- “没有分支引用就立即删除提交。” 分支不可达与对象删除不同,reflog 和垃圾回收决定真实仓库中能恢复多久。
模型省略的部分
本课模拟干净的本地仓库,用 --allow-empty 在固定空树上提交,避免文件、索引变化遮住引用更新。作者、提交者固定,每个提交的时间为 1700000000 加其序号,以产生可重复 ID。模型省略检出冲突、远程与上游引用、标签、合并、reflog 界面、引用打包、分支删除和改名、钩子、签名、替换引用、并发文件系统写入、垃圾回收。
这些限制不改变对象不可变、分支可移动、符号 HEAD 命名分支、分离 HEAD 直接命名提交的规则。对象模型可参看提交快照课程,引用细节见 gitglossary、git branch、git switch。