ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

12 — 分支就是便利贴,HEAD 就是指路牌

2026/8/10 12:06:18 拓冰建站 浏览量
12 — 分支就是便利贴,HEAD 就是指路牌 12 — 分支就是便利贴HEAD 就是指路牌摘要本文深入解析 Git 中「引用」和「HEAD」的核心概念。分支本质上是.git/refs/heads/目录下的文件仅包含一行 commit 哈希值如同贴在档案上的便利贴HEAD 则是.git/HEAD文件指示当前所在位置如同指路牌。文章通过「便利贴」和「指路牌」的生动比喻拆解了分支创建、切换、删除及提交时的底层文件变化解释了分离 HEAD 的状态与风险并提供了动手实验和真实场景示例帮助读者从文件系统层面理解 Git 引用机制从而掌握分支操作的底层逻辑。写在前面这一章要解决什么你可能已经用了很多次分支——git branch、git switch——但你有没有想过分支到底是什么它是复制了一份文件还是复制了一个目录都不是。分支就是一张便利贴。这一章帮你彻底搞清楚「引用」和「HEAD」到底是什么为什么理解了它们Git 的很多操作就不再神秘。学完后你应该能用自己的话说出「分支是什么」不是复制文件夹是便利贴分清 HEAD、分支引用、远程跟踪引用的区别知道「分离 HEAD」是什么状态怎么进去、怎么出来看懂.git/refs/目录里的文件内容读者设定大一同学已经学过分支操作第 05 章和对象模型第 10 章但还不理解分支的底层逻辑。1. 定位为什么要搞懂引用1.1 一句话先记住分支是一个文件里面只有一行文字——某个 commit 的哈希值。就像便利贴上只写了一个编号贴在档案柜的某个隔层上。1.2 不知道引用是什么会怎样不理解引用理解了之后以为开分支会复制整个项目知道分支只是多了一张便利贴秒开以为删分支会丢数据知道删分支只是撕了便利贴文件还在看到「分离 HEAD」就慌知道只是 HEAD 直接指向了一个 commit不是出错了不知道为什么git push有时候被拒绝知道推送是移动远程引用有冲突就不让推1.3 和你已经会的事对比你已经会的和这一章的关系第 05 章的分支操作这一章告诉你分支的底层是什么第 09 章的.git/目录结构这一章带你看.git/refs/里的引用文件第 11 章的「命令与文件变化」这一章深入看「移动引用」到底移动了什么2. 本质引用到底长什么样2.1 亲眼看一个分支文件打开你的 Git 仓库执行cat.git/refs/heads/main你会看到类似这样的输出a1b2c3d4e5f6...40 个字符的哈希值就这么一行。这就是整个main分支——一个文件里面写着它指向的那个 commit 的哈希值。白话翻译main这张便利贴上写的是「第 a1b2c3 号存档」。2.2 引用的层级Git 里有好几层引用它们的关系就像这样HEAD指路牌你现在在哪 │ ├── 指向分支引用如 main │ └── 指向某个 commit │ └── 或者直接指向某个 commit这就是「分离 HEAD」引用存在哪指向什么白话HEAD.git/HEAD当前分支或当前 commit你现在站的位置指路牌分支引用.git/refs/heads/分支名某个 commit贴在档案上的便利贴远程跟踪引用.git/refs/remotes/origin/分支名远程的某个 commit别人档案上的便利贴只读标签引用.git/refs/tags/标签名某个 commit贴着版本号的便利贴2.3 HEAD最重要的引用HEAD 是 Git 里最核心的概念之一。它告诉 Git「你现在在哪」。正常情况下HEAD 指向一个分支名cat.git/HEADref: refs/heads/main白话翻译我站在main这个标签上。当你提交新 commit 时Git 会做两件事创建新的 commit 对象把 HEAD 指向的分支引用移动到新 commit所以是分支跟着你一起往前走——因为 HEAD 指向分支分支指向新 commit。图引用分支、标签指向 commit 对象。图分支只是指向某个 commit 的指针。3. 分支操作底层拆解3.1 创建分支git branch feature底层在.git/refs/heads/下新建一个文件feature内容是当前 commit 的哈希。白话多贴了一张叫feature的便利贴贴的位置和main的便利贴在同一页。gitbranch feature# 查看两个分支是否指向同一个 commitcat.git/refs/heads/maincat.git/refs/heads/feature两个文件的哈希值完全相同——这就是分支创建瞬间「两个标签贴在同一页」。3.2 切换分支git switch feature底层把.git/HEAD的内容从ref: refs/heads/main改成ref: refs/heads/feature然后根据feature指向的 commit 还原工作区和暂存区。白话你从main标签走到了feature标签看到的文件变成了feature这个位置的。# 切换前cat.git/HEAD# 输出ref: refs/heads/maingitswitch feature# 切换后cat.git/HEAD# 输出ref: refs/heads/feature3.3 删除分支git branch -d feature底层删除.git/refs/heads/feature这个文件。白话撕掉feature这张便利贴。档案柜里的东西一个都没动。注意如果feature指向的 commit 没有其他引用或 reflog 指向它这些对象最终会被垃圾回收。但git branch -d有安全检查——只有当分支已经合并到当前分支时才允许删除。3.4 提交时发生了什么gitswitch featureecho新功能feature.txtgitaddfeature.txtgitcommit-m添加新功能底层创建 blob 对象存 feature.txt 的内容创建 tree 对象记录新的目录结构创建 commit 对象指向新的 treeparent 是上一个 commit把feature引用移动到新 commitHEAD 继续指向feature而feature已经移到新 commit 了白话你在feature标签的位置上新增了一份存档然后把feature标签往前挪到了这份新存档上。main标签还在原来的位置没有动。# 验证main 和 feature 已经不同了cat.git/refs/heads/main# 旧哈希cat.git/refs/heads/feature# 新哈希4. 分离 HEADHEAD 不指向分支了4.1 什么是分离 HEAD正常情况下HEAD 指向一个分支名。但如果你直接 checkout 到某个 commitgitcheckout a1b2c3d此时 HEAD 不再指向分支而是直接指向那个 commitcat.git/HEAD# 输出a1b2c3d4e5f6...直接是哈希不再是 ref: refs/heads/...白话指路牌不再指向某个标签而是直接指向档案柜里的某一页。4.2 分离 HEAD 的风险在分离 HEAD 状态下提交 commitecho测试test.txtgitaddtest.txtgitcommit-m分离 HEAD 下的提交这次提交会创建一个新的 commit但没有分支指向它如果你切走了git switch main就再也找不到它了——除非你记住了哈希或者 reflog 里有记录。白话你在一页没有标签的档案上写了字走开之后不知道怎么翻回那一页了。4.3 什么时候会进入分离 HEAD操作场景git checkout 哈希你想看某个历史版本git checkout 标签名你想看某个打标签的版本rebase 过程中Git 暂时把 HEAD 指向中间 commitgit submodule操作子模块的默认状态4.4 怎么离开分离 HEAD方法一如果你在分离 HEAD 下做了提交想保留gitbranch save-my-workgitswitch save-my-work白话给当前位置贴个标签然后站到那个标签上去。方法二如果没有做新提交直接切回去gitswitch main方法三用 reflog 找回丢失的提交gitreflog# 找到你提交的哈希然后切过去建分支gitswitch-crecovered哈希5. 动手准备建一个可丢弃的练习目录mkdirlearn-git-12cdlearn-git-12gitinit-bmainecho第一版f.txtgitaddf.txtgitcommit-m第一次提交echo第二版f.txtgitaddf.txtgitcommit-m第二次提交演示身份Ada Example adaexample.com6. 跟着做实验 1亲手看引用文件cat.git/HEAD# 输出ref: refs/heads/maincat.git/refs/heads/main# 输出某个 40 字符的哈希gitbranch featurecat.git/refs/heads/feature# 输出和 main 相同的哈希实验 2体验分离 HEADgitlog--oneline# 假设输出a1b2c3 第二次提交# d4e5f6 第一次提交gitcheckout d4e5f6cat.git/HEAD# 输出d4e5f6...直接是哈希不再是 ref: 开头gitswitch main# 回到正常状态实验 3在分支上提交观察引用移动gitbranchtestgitswitchtestecho测试内容test.txtgitaddtest.txtgitcommit-m在 test 上提交# main 没动cat.git/refs/heads/main# test 移动了cat.git/refs/heads/test# 两个哈希不同了7. 对照表各种引用的区别引用类型文件位置谁创建能手动改吗会不会被推送HEAD.git/HEADGit 自动能改但不要不会本地分支.git/refs/heads/名称你能改但不建议不会推送后远程有对应追踪分支远程跟踪分支.git/refs/remotes/origin/名称git fetch不应该改这是远程的镜像标签.git/refs/tags/名称你用git tag创建能改但不建议推送时可以推标签reflog.git/logs/Git 自动不建议改不会8. 安全习惯规矩为什么不要手动编辑.git/refs/下的文件手滑写错哈希会导致引用损坏分离 HEAD 下做了提交要立刻建分支否则切走后可能找不到这些提交用git switch而不是git checkoutswitch语义更明确不会意外进入分离 HEAD定期检查git branch -a看看有哪些分支和远程追踪分支9. 真实场景场景一用分离 HEAD 看历史版本老师让你看三个月前的代码长什么样gitlog--oneline# 找到那个 commit 的哈希gitcheckout哈希# 进入分离 HEAD# 看代码、运行代码……gitswitch main# 看完了回到正常状态场景二误删了分支怎么恢复# 不小心删了一个还没合并的分支gitbranch-Dimportant-work# 恢复用 reflog 找到最后一次指向它的 commitgitreflog# 找到哈希后gitbranch important-work哈希场景三为什么推送有时被拒绝你本地main指向 commit A远程origin/main指向 commit B别人推的新代码。你执行git pushGit 发现远程引用要「倒退」才能接受你的提交所以拒绝了。正确做法先git pull合并远程新代码再 push。10. 进阶补充首读可跳过10.1 符号引用Git 提供了几个特殊的符号引用不需要记哈希符号含义HEAD当前位置HEAD~1当前位置的上一代父提交HEAD~3当前位置往上数第 3 代HEAD^父提交merge commit 的第一个父HEAD^2merge commit 的第二个父HEAD 的简写{2}HEAD 2 步之前的位置reflog10.2 packed-refs当分支很多时Git 会把引用打包到一个文件.git/packed-refs里以提升性能。这不影响使用但如果你找不到.git/refs/heads/某个分支可能它在 packed-refs 里。10.3 远程引用不会自动更新origin/main只有在你git fetch或git pull时才会更新。它不会因为你本地提交了就变——它反映的是「远程那边最新到哪了」。11. 小实验实验 A亲手看引用建一个仓库做两次提交cat .git/HEAD看 HEAD 指向什么cat .git/refs/heads/main看分支引用指向什么创建新分支确认新分支和 main 指向同一个 commit通过标准能说出 HEAD 指向分支名分支名指向 commit 哈希。实验 B体验分离 HEADgit checkout到一个历史 commit确认 HEAD 直接指向哈希而不是分支名做一个新提交切回 main用git reflog找回刚才的提交通过标准理解分离 HEAD 下提交的 commit 可能丢失知道用 reflog 找回。实验 C引用移动追踪在 main 上提交一次创建并切换到 feature 分支在 feature 上提交一次分别查看 main 和 feature 的哈希确认 main 没动、feature 移动了通过标准理解「在哪个分支上提交哪个分支的引用就移动」。12. 常见问题问分支和标签有什么区别分支是会移动的便利贴——每次提交它就往前挪。标签是贴好了就不动的便利贴——它永远指向你打标签那一刻的 commit。问远程跟踪分支能改吗不应该手动改。它是远程仓库的镜像只有git fetch才应该更新它。问分离 HEAD 下的提交会丢吗切走之后没有引用指向它但 reflog 会记住 90 天。90 天内可以用git reflog找回来。超过 90 天且没有引用指向它垃圾回收会清理掉。问为什么git checkout会进入分离 HEAD但git switch不会git switch只接受分支名不接受裸哈希。如果你想看历史的某个 commit用git checkout。但日常切换分支用git switch更安全。问.git/refs/heads/里没有某个分支的文件可能被打包到了.git/packed-refs文件里。用git branch查看所有分支不需要去文件系统里找。13. 总结13.1 一页速记项目要点分支.git/refs/heads/名称下的一个文件内容是 commit 的哈希HEAD.git/HEAD文件通常指向某个分支名分离 HEADHEAD 直接指向 commit 哈希不指向任何分支创建分支新建一个引用文件内容是当前 commit 的哈希删除分支删除引用文件不影响对象提交时在哪个分支上提交哪个分支的引用就移动到新 commit13.2 本系列中的位置10 对象模型 → 11 命令与文件 → 12 引用与 HEAD你在这里 → 13 暂存区深入 ↑ 搞懂「移动引用」到底移动了什么13.3 思维升华分支是便利贴HEAD 是指路牌。记住这个比喻Git 80% 的操作你都能理解创建分支 贴标签切换分支 走到另一个标签提交 往前挪标签。简单吧13.4 延伸阅读Pro Git — Git 引用gitrevisions 手册符号引用的完整说明git-switch 文档git-checkout 文档本仓库图示署名assets/diagrams/ATTRIBUTION.md13.5 检查清单能说出分支文件只包含一行哈希能区分 HEAD 指向分支名 vs HEAD 直接指向哈希知道分离 HEAD 是什么、怎么进入、怎么离开知道删分支只是删引用对象不会丢完成了实验 A、B、C