git分支本质是指向commit的可变指针,存储为.git/refs/heads/下的文本文件,仅含40位sha-1哈希值;创建、切换、删除分支均不复制文件,仅操作指针,head始终指向分支引用而非直接指向commit。

分支本质就是一个指向 commit 的可变指针
Git 分支不是副本、不是快照、更不是文件夹,它只是一个文本文件(.git/refs/heads/<branch-name></branch-name>),里面只存着一个 40 位 SHA-1 哈希值——也就是某个 commit 对象的 ID。你执行 git branch feature,Git 就只是在 .git/refs/heads/feature 里写入当前 HEAD 指向的 commit ID;不涉及复制任何文件或历史。
这意味着:
- 创建分支极快,和项目大小无关
- 切换分支时 Git 会重置工作目录文件内容,是因为要匹配目标分支最新 commit 的快照,不是因为“切换了指针”本身触发了文件操作
-
git branch -d删除分支,只是删掉那个文本文件,不删 commit(除非 commit 已不可达)
HEAD 是如何决定“当前分支”并驱动指针移动的
HEAD 是一个符号引用(symbolic ref),默认内容是 ref: refs/heads/master。当你执行 git checkout feature,Git 实际上只是把 HEAD 文件的内容改成 ref: refs/heads/feature;之后所有 git commit 都会更新 feature 文件里的哈希值,而 master 完全不动。
常见误解:
- “切换分支后 HEAD 指向 commit” → 错。HEAD 永远指向分支引用(如
refs/heads/main),只有在分离头指针(detached HEAD)状态下才直接指向 commit - “提交会自动改 HEAD” → 不准确。提交会先读取 HEAD 当前指向的分支名,再更新那个分支文件;HEAD 自身内容不变
-
git log --oneline --decorate --graph --all能直观看到各分支指针位置,比靠记忆靠谱得多
为什么新分支刚建好时和原分支指向同一个 commit
执行 git branch dev 时,Git 会读取 HEAD 当前解析出的分支(比如 main),再读取 .git/refs/heads/main 里的 commit ID,最后把该 ID 写进 .git/refs/heads/dev。所以 dev 和 main 初始必然指向同一 commit。
这个设计带来两个关键行为:
- 新分支立即能访问所有已有文件(因为共享历史起点)
- 后续在
dev上的提交只移动dev指针,main指针纹丝不动 —— 这才是并行开发的基础 - 如果误在
main上提交后再建分支,新分支会“错过”这次提交,除非显式指定 commit ID:git branch bugfix abc1234
指针移动的唯一触发条件:git commit 或 git reset
分支指针不会因 git add、git status、git fetch 或 git merge(未产生新 commit 时)而移动。只有两种操作会真正改写分支引用文件:
-
git commit:在当前分支上创建新 commit,并把该分支指针更新为新 commit ID -
git reset --hard <commit></commit>:强制把当前分支指针重设为指定 commit(同时重置暂存区和工作区)
注意:git merge 是否移动指针,取决于是否产生 fast-forward 合并:
- 如果是 fast-forward(目标分支无分叉),
git merge等价于git reset --hard <target-commit></target-commit>,当前分支指针前移 - 否则会生成一个合并 commit,当前分支指针指向这个新 commit
真正容易被忽略的是:分支指针移动完全不依赖远程仓库状态。本地 main 指针是否和 origin/main 一致,得靠 git fetch 后手动对比 git log main..origin/main —— Git 不会自动同步指针。











