跳到主要内容
预计阅读 24 分钟

分支:平行宇宙的管理术

想象一下,你正在写一本小说。写到第五章时,你突然冒出一个大胆的想法:如果主角在这里做了完全不同的选择,故事会怎样发展?你不想丢掉已有的情节线,又想自由地探索新的可能性——如果能同时维护两条故事线,需要哪条就用哪条,那该多好?Git 的分支系统,正是这样一套”平行宇宙管理术”。

开篇自测:你已经知道多少?

  1. Git 的分支在底层究竟是什么?创建一个分支需要消耗多少磁盘空间?
  2. HEAD 是什么?它和分支、提交之间是怎样的关系?
  3. git switchgit checkout 有什么区别?为什么 Git 2.23 之后推荐用 git switch
  4. 远程分支(如 origin/main)和本地分支有什么关系?什么是”跟踪分支”?
  5. 你的团队有没有分支命名规范?feature/bugfix/hotfix/ 各自代表什么?

一、分支的本质:一张写着门牌号的便签纸

很多人第一次接触”分支”这个概念时,会把它想象成一份完整的代码副本——好像每创建一个分支,Git 就把整个项目复制了一份。如果真是这样,一个有着上百个分支的大型仓库岂不是要占用惊人的磁盘空间?

事实远比你想象的优雅。

**分支在 Git 中,不过是一个指向某个 commit 的可移动指针。**它的物理存在,就是 .git/refs/heads/ 目录下的一个小文件,里面存储着一个 40 字符的 SHA-1 哈希值加上一个换行符——总共 41 个字节。

# 看看分支文件的真面目
$ cat .git/refs/heads/main
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

$ wc -c .git/refs/heads/main
41

是的,创建一个分支的代价,就是在磁盘上写入 41 个字节。这就像在你的”平行宇宙管理器”上贴了一张便签纸,上面只写了一个门牌号(commit 的哈希值),告诉你”这个宇宙目前发展到了这个节点”。

分支的本质:

    main ──────► a1b2c3d  (commit)

    feature/login ─►│


              项目的某个快照

因为创建分支几乎没有成本,所以你应该养成频繁创建分支的习惯——想试一个新想法?开个分支。要修一个 bug?开个分支。千万别觉得”这点小改动不值得开分支”,在 Git 里,开分支就像呼吸一样自然。


二、HEAD:你站在哪个宇宙里?

如果分支是”平行宇宙的入口”,那么 HEAD 就是”你本人当前站在哪个宇宙里”的标记。

HEAD 是一个特殊的引用,它告诉 Git:当前工作目录对应的是哪个 commit。但 HEAD 通常不是直接指向某个 commit,而是指向一个分支名,再由分支名指向具体的 commit。这形成了一条链:

HEAD → main → a1b2c3d (commit)

你可以随时查看这条链的内容:

# HEAD 指向哪个分支?
$ cat .git/HEAD
ref: refs/heads/main

# 这个分支又指向哪个 commit?
$ cat .git/refs/heads/main
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

当你在当前分支上做一次新的提交时,会发生两件事:

  1. Git 创建一个新的 commit 对象,它的父节点是当前 commit
  2. 分支指针自动向前移动,指向这个新 commit
提交前:HEAD → main → C3

提交后:HEAD → main → C4


                      C3 (C4 的父节点)

HEAD 就像是你在平行宇宙中的”定位器”——无论你跳转到哪个宇宙(切换分支),它都会跟着你走,确保你始终知道自己身在何处。

想一想 如果 HEAD 直接指向了一个 commit 而不是分支(称为”分离 HEAD”状态),会发生什么?在这种状态下做的提交会归属于哪个分支?


三、分支的日常操作

3.1 创建分支

创建分支的最基本方式:

# 在当前 commit 上创建一个新分支(不会切换过去)
$ git branch feature/user-profile

# 基于特定的 commit 或分支创建
$ git branch bugfix/login-error main
$ git branch experiment abc1234

此时你只是创建了一个新的”平行宇宙入口”,但你本人还站在原来的宇宙里——HEAD 依然指向之前的分支。

3.2 切换分支:从 checkout 到 switch 的演进

在 Git 的早期版本中,切换分支使用 git checkout

$ git checkout feature/user-profile

checkout 是一个身兼多职的命令——它既能切换分支,又能恢复文件内容,还能创建新分支。这种设计让很多新手困惑:同一个命令在不同场景下做完全不同的事情。

从 Git 2.23 版本开始,Git 引入了两个新命令来分担 checkout 的职责:

场景旧命令新命令(Git 2.23+)
切换分支git checkout <branch>git switch <branch>
恢复文件git checkout -- <file>git restore <file>
创建并切换git checkout -b <branch>git switch -c <branch>

推荐使用 git switch,因为它的语义更加清晰——这个命令就是用来切换分支的,不会和其他功能混淆。

3.3 git switch 详解

# 切换到已有分支
$ git switch feature/user-profile

# -c (--create):创建新分支并立即切换过去
$ git switch -c feature/payment

# 基于远程分支创建本地分支并切换
$ git switch -c feature/search origin/feature/search

# --detach:进入"分离 HEAD"状态,直接定位到某个 commit
$ git switch --detach v2.0.0
$ git switch --detach abc1234

# 快速切回上一个分支(就像 cd - 回到上一个目录)
$ git switch -

--detach 适合什么场景?当你只是想查看某个历史版本或标签的代码,而不打算在上面开发时,--detach 是最合适的选择。它就像是以”访客模式”参观一个平行宇宙——你可以四处看看,但不会留下痕迹。

常见误区

  • 误区一git switch -c-c 容易和 git checkout -b-b 搞混。记住:switch 用 -c(create),checkout 用 -b(branch)。
  • 误区二:认为 git checkout 已经被废弃了。事实上 checkout 依然可用,switchrestore 只是提供了更清晰的替代方案。在脚本和旧文档中你仍然会大量看到 checkout

3.4 查看分支

# 查看本地分支(当前分支前有 * 号)
$ git branch
* main
  feature/user-profile
  bugfix/login-error

# 查看所有分支(包括远程跟踪分支)
$ git branch -a
* main
  feature/user-profile
  remotes/origin/main
  remotes/origin/feature/search

# 查看每个分支的最新提交信息
$ git branch -v
* main               a1b2c3d 更新首页样式
  feature/user-profile d4e5f6a 添加用户头像上传功能

# 查看已合并/未合并到当前分支的分支
$ git branch --merged
$ git branch --no-merged

3.5 删除分支

当一个功能开发完成并合并后,对应的分支就完成了使命,应该及时删除:

# 删除已合并的分支(安全删除)
$ git branch -d feature/user-profile

# 强制删除(即使未合并,确认不要了)
$ git branch -D experiment/crazy-idea

# 删除远程分支
$ git push origin --delete feature/user-profile

-d(小写)是一道安全门——如果分支上的改动还没有被合并到其他分支,Git 会拒绝删除并给出警告。只有当你用 -D(大写)明确表示”我知道这些改动会丢失,我就是要删”时,Git 才会执行。


四、远程分支与跟踪分支

4.1 远程跟踪分支

当你克隆一个仓库或者执行 git fetch 时,Git 会在本地创建一组以 origin/ 开头的引用,比如 origin/mainorigin/develop。这些被称为远程跟踪分支(remote-tracking branch)。

它们是远程仓库上各分支状态的本地快照——像照片一样记录了你上次与远程仓库同步时,那边的分支指向哪个 commit。

本地仓库                          远程仓库 (origin)
─────────                        ─────────────────
main ────────► C5                main ────────► C5
origin/main ──► C5               develop ─────► C7
origin/develop ► C7
feature/x ───► C6

关键点origin/main 不会随着你本地的操作自动更新。只有当你执行 git fetchgit pull 时,它才会去远程仓库”拍一张新照片”回来。

4.2 设置跟踪关系

当本地分支和远程分支建立了跟踪关系后,git pullgit push 就知道该和哪个远程分支同步了:

# 方式一:推送时建立跟踪(最常用)
$ git push -u origin feature/payment
# -u 是 --set-upstream 的简写

# 方式二:手动设置已有分支的跟踪关系
$ git branch --set-upstream-to=origin/main main

# 方式三:创建分支时直接基于远程分支(自动建立跟踪)
$ git switch -c feature/search origin/feature/search

# 查看所有分支的跟踪关系
$ git branch -vv
* main            a1b2c3d [origin/main] 更新首页样式
  feature/payment d4e5f6a [origin/feature/payment: ahead 2] 添加支付功能

上面的 ahead 2 表示本地分支比远程跟踪分支领先了 2 个提交(还没推送上去)。你也可能看到 behind 3(远程有 3 个新提交你还没拉取)或 ahead 1, behind 2(双方都有新提交,需要同步)。


五、分支命名规范

一个好的分支命名规范,就像城市的门牌号系统——让任何人一眼就能知道这个分支是做什么的。下面是业界广泛采用的一套约定:

feature/   ── 新功能开发
bugfix/    ── 常规缺陷修复
hotfix/    ── 紧急线上问题修复
release/   ── 版本发布准备
refactor/  ── 代码重构
docs/      ── 文档更新
test/      ── 测试相关

命名示例:

feature/user-authentication
feature/JIRA-1234-shopping-cart
bugfix/login-redirect-loop
hotfix/payment-timeout-crash
release/v2.3.0

一些实用的命名原则:

  • 使用斜杠分隔类别feature/xxx 在很多 Git 工具中会被折叠显示成目录结构,便于浏览
  • 使用短横线连接单词user-profile 而不是 user_profileuserProfile
  • 关联任务编号:如果团队使用 Jira 等项目管理工具,在分支名中包含 issue 编号(如 feature/PROJ-456-add-export
  • 保持简洁:分支名是给人看的,别写成一篇作文

六、可视化分支关系

当分支多了之后,你可能会想看看各个分支之间的关系。git log 加上几个参数,就能在终端里画出漂亮的分支图:

# 经典的分支可视化命令
$ git log --graph --oneline --all

* 7f3b2a1 (HEAD -> main) Merge branch 'feature/payment'
|\
| * d4e5f6a (feature/payment) 添加支付回调处理
| * c3d4e5f 实现微信支付接口
|/
* a1b2c3d 更新首页样式
* 9e8d7c6 修复导航栏样式
| * f1e2d3c (feature/search) 实现搜索建议功能
| * e0d1c2b 添加搜索API对接
|/
* 8a7b6c5 项目初始化

这就像从高处俯瞰所有平行宇宙——你能清楚地看到它们在哪里分叉、在哪里交汇。

如果你觉得每次都输入这么长的命令太麻烦,可以设置一个别名:

$ git config --global alias.graph "log --graph --oneline --all --decorate"

# 以后只需要
$ git graph

想一想 在上面的 git log --graph 输出中,你能看出哪些分支已经合并回了 main,哪些还在独立开发中吗?这种”已合并 vs 未合并”的视角,对于日常的分支管理非常重要。


七、实战场景:同时开发多个功能

假设你是一个全栈开发者,今天需要同时推进三项工作:

  1. 一个新的用户资料页功能(预计 3 天)
  2. 一个紧急的登录 bug 修复(必须今天完成)
  3. 一个实验性的暗黑模式(不确定能不能上线)

这是分支管理大显身手的时候。来看看整个流程:

# 当前在 main 分支上,先确保代码是最新的
$ git pull origin main

# 1. 为用户资料页开一个功能分支
$ git switch -c feature/user-profile
# 写了一些代码...
$ git add src/pages/UserProfile.vue
$ git commit -m "feat: 添加用户资料页基础布局"

# 2. 突然接到紧急 bug 报告!切回 main,开 hotfix 分支
$ git switch main
$ git switch -c hotfix/login-failure
# 定位并修复 bug...
$ git add src/auth/login.js
$ git commit -m "fix: 修复第三方登录回调地址错误"
# 修完了,推送并提交 PR
$ git push -u origin hotfix/login-failure

# 3. 等 hotfix 合并后,切回功能开发
$ git switch feature/user-profile
# 继续写代码...

# 4. 下午有点灵感,想试试暗黑模式
$ git switch main
$ git switch -c experiment/dark-mode
# 实验一番...
$ git commit -m "experiment: 暗黑模式原型"

# 5. 一天结束,看看各分支的状态
$ git branch -v
  experiment/dark-mode  f1e2d3c experiment: 暗黑模式原型
  feature/user-profile  a1b2c3d feat: 添加用户资料页基础布局
* hotfix/login-failure  c3d4e5f fix: 修复第三方登录回调地址错误
  main                  9e8d7c6 最新的稳定版本

整个过程中,每条”故事线”都互不干扰。修紧急 bug 时不用担心弄乱正在开发的新功能,做实验时也不用担心污染主分支。这就是分支的力量——让你在不同的任务之间自由切换,而每个任务都有自己独立的空间。


八、常见误区与最佳实践

常见误区

  • 误区一:在 main 上直接开发。 main(或 master)分支应该始终保持可部署状态。所有的开发工作都应该在功能分支上进行,通过 Pull Request 审查后再合并回 main。把 main 想象成”已出版的小说正文”——你不会直接在出版物上涂改,而是在草稿纸上修改好了再替换。

  • 误区二:分支存活太久。 一个分支存在的时间越长,它和 main 的差异就越大,合并时发生冲突的概率就越高。就像两个平行宇宙分离得越久,重新交汇时需要调和的矛盾就越多。尽量把分支的生命周期控制在几天以内,定期从 main 同步最新代码。

  • 误区三:忘记切换分支就开始写代码。 这是一个非常常见的错误——你以为自己在功能分支上,实际上还在 main 上。写完代码才发现,所有改动都跑到了 main 上。防范方法:在终端提示符(prompt)中显示当前分支名,养成动手写代码前先 git status 的习惯。如果真的出了这个问题,别慌——可以用 git stash 暂存改动,切到正确的分支再 git stash pop 恢复。

  • 误区四:把 git branch -D 当成常规操作。 大写 -D 是强制删除,会跳过”是否已合并”的安全检查。只有在你确信不需要那个分支的改动时才用它。日常删除请用小写 -d


掌握度自测

  1. Git 中一个分支在磁盘上实际占用多少空间?

    • A) 和项目代码大小相同(完整复制)
    • B) 约 41 字节(一个 SHA-1 哈希值 + 换行符)
    • C) 约 1 MB(存储差异信息)
    • D) 取决于分支上的提交数量
  2. HEAD 通常的指向方式是:

    • A) HEAD 直接指向一个 commit
    • B) HEAD 指向一个分支名,分支名再指向 commit
    • C) HEAD 指向工作目录
    • D) HEAD 指向暂存区
  3. 以下哪个命令可以创建新分支并立即切换过去?(Git 2.23+)

    • A) git branch -s new-feature
    • B) git switch -c new-feature
    • C) git switch -b new-feature
    • D) git switch --new new-feature
  4. origin/main 是什么?

    • A) 远程仓库上的 main 分支本身
    • B) 本地对远程 main 分支状态的快照(远程跟踪分支)
    • C) 一个自动同步的本地分支
    • D) origin 服务器的根目录
  5. 以下关于分支删除的说法,正确的是:

    • A) git branch -d 可以删除任何分支,包括未合并的
    • B) git branch -D 会同时删除远程分支
    • C) git branch -d 会拒绝删除未合并的分支,-D 则强制删除
    • D) 删除分支会删除该分支上所有的 commit

自我评估

  • 答对 5 题:分支管理的基础概念已经非常清晰!可以进入下一章学习分支的合并策略了。
  • 答对 3-4 题:核心概念理解不错,建议回顾一下 HEAD 的引用机制和远程跟踪分支的部分。
  • 答对 0-2 题:分支是 Git 中最重要的概念之一,建议仔细重读”分支的本质”和”HEAD”这两节,理解透了再往下走。

参考答案: 1-B, 2-B, 3-B, 4-B, 5-C

购买课程解锁全部内容

版本控制不翻车:Git 从基础到团队协作

¥29.90