第 5 章:平行宇宙——分支的魔法
约 3077 字大约 10 分钟
2025-12-25
你准备给项目加一个登录系统。前端页面、后端接口、数据库校验——工作量不小,可能要改几十个文件。
但你现在的 main 分支上跑着一套能正常工作的代码。直接在它上面开工?万一写到一半发现方向错了,或者改出了一堆 bug,连带着把原本好好的代码也污染了。撤都撤不干净。
你需要的是一块安全沙盒——跟主线完全隔离,在里面随便折腾,搞砸了也不影响主线,搞好了再合并回去。
这就是 Git 分支要做的事。
本章目标
理解 Git 最强大的功能——分支(Branch),学会在不影响主线的情况下开发新功能,并掌握冲突解决的完整流程。
一、什么是分支?
把项目想象成你在写的一本小说:
- 主线(
main分支):正式出版的故事线,读者正在看。 - 分支(
feature分支):你突然想写一个"主角黑化"的番外篇。在番外里,你让主角领便当都没问题。
番外写完之后,你有两个选择:
- 写得好,合并(Merge) 回主线,从此主角有了暗黑人格。
- 写得烂,直接删掉这个分支,主线剧情纹丝不动。
分支的核心意义就三个字:隔离开发,互不干扰。
Git 分支为什么秒建?—— 41 字节的秘密
如果你用过 SVN(Subversion),你大概率对"创建分支"有心理阴影。在 SVN 里,分支是对整个项目目录的物理拷贝——项目多大,拷贝就多慢,硬盘占用翻倍。SVN 用户每次建分支都要深呼吸,泡杯咖啡等着。
然后他们遇到了 Git,第一次敲下 git branch,分支瞬间就建好了。咖啡还没泡好。他们以为命令没执行成功,又敲了一遍——然后发现真的已经建好了。
原因是:Git 的分支只是一个 41 字节的文本文件,里面存的不是代码,而是一个 Commit ID(SHA-1 哈希值)。这 41 字节说了一句话:"这个分支指向某某提交"。
ref: refs/heads/main无论你的项目是 10MB 还是 10GB,创建分支永远是往 .git/refs/heads/ 目录下写一个 41 字节的文件。这就是为什么 Git 鼓励你疯狂用分支——它真的太便宜了,便宜到你可以为一个拼写错误专门开一个分支。
二、分支操作实战
2.1 查看分支
git branch输出类似:
* main前面带 * 的表示你当前所在的分支。刚初始化的仓库默认只有 main。
2.2 创建并切换分支
假设我们要开发登录功能,起名为 login:
# 创建并切换到 login 分支
git checkout -b login这条命令其实等于下面两步的合体:
git branch login # 创建分支
git checkout login # 切换过去现在,你已经站在 login 这个平行宇宙里了。终端的提示符会变,Git 也会告诉你在哪个分支上。
2.3 在分支上搞事情
在 login 分支下,新建一个文件 login.sh:
echo "PHP 是世界上最好的语言" > login.sh
git add .
git commit -m "feat: 完成登录功能"此时,login 分支比 main 多了一次提交。在 login 看来,login.sh 是真实存在的。但 main 对此一无所知。
2.4 切换回主线
git checkout main去看你的文件夹——login.sh 消失了!
别慌。它没有丢,它还在 login 分支里。Git 在你切换分支时,把工作区的内容替换成了 main 分支指向的版本。main 的版本里没有 login.sh,所以你看不到它。切回 login,它又会出现。
这就是 Git 的隔离机制:每个分支维护自己独立的文件快照,互不影响。你的操作系统里其实只有一个 .git 文件夹和一套文件,但 Git 通过指针魔术让你觉得同时存在多个版本的代码。
2.5 合并分支(Merge)
login 功能写好了,该把它接回主线了。先确保你站在 main 上(你是要把别人的东西拉进来,不是把自己的推出去):
git checkout main
git merge loginGit 通常会输出 Fast-forward(快进模式)。这是因为 main 在 login 开发期间没有任何新提交,Git 只需要把 main 的指针往前挪到 login 的位置——连合并都算不上,就是挪了一下指针。
再去看文件夹:login.sh 回来了。
2.6 删除分支
login 已经完成了它的历史使命,功成身退:
git branch -d login-d 是安全删除——如果分支还没有被合并,Git 会拒绝删除并提醒你,防止误操作。如果确定不要了,用 -D 强制删除。
三、解决冲突(Conflict)
合并不总是一帆风顺。当两个分支同时修改了同一个文件的同一行,冲突就来了。
冲突是怎么发生的?
假设:
- 你在
main分支上把login.sh的第一行改成了echo "Python 才是"。 - 同时,你在
dev分支上也把login.sh的第一行改成了echo "Rust 最强"。
现在你想把 dev 合并到 main:
git checkout main
git merge devGit 懵了:"你们俩都改了同一行,我到底听谁的?"
Auto-merging login.sh
CONFLICT (content): Merge conflict in login.sh
Automatic merge failed; fix conflicts and then commit the result.合并失败,Git 进入冲突状态。
冲突文件长什么样?
打开 login.sh,里面变成了这样:
<<<<<<< HEAD
echo "Python 才是"
=======
echo "Rust 最强"
>>>>>>> dev这些标记的含义:
| 标记 | 含义 |
|---|---|
<<<<<<< HEAD | 当前分支(你执行 merge 时所在的分支)的内容从这里开始 |
======= | 分隔线 |
>>>>>>> dev | 被合并分支的内容到这里结束 |
HEAD 是你当前在的 main 分支,所以上半部分是 main 的版本("Python 才是")。下半部分是 dev 的版本("Rust 最强")。
一个好用的类比
Git 的冲突标记就像 Java 的 NullPointerException——但比它良心多了。
NullPointerException 只告诉你"有个东西是 null",然后甩给你一行堆栈,让你自己大海捞针去找到底哪个对象没初始化。
而 Git 的冲突标记直接告诉你:"这两个版本在第 N 行打架了,这是 A 的版本,这是 B 的版本,你自己决定留哪个。" 定位精准,上下文完整,堪称异常信息的模范生。
解决冲突(手动操作)
第一步:打开冲突文件。
决定留哪个:
- 留
main的版本 → 删掉dev的部分和所有标记 - 留
dev的版本 → 删掉main的部分和所有标记 - 两个都要 → 删掉标记,把两行代码都留下
- 两个都不要 → 删掉标记,自己重写
第二步:清理标记。
把 <<<<<<<、=======、>>>>>>> 这三行标记符号全部删除,只保留你最终想要的代码。假设你决定两边都不要,写一个新版本:
echo "都别争了,写 TypeScript"保存文件。
第三步:标记为已解决。
git add login.sh对 Git 来说,git add 就是在说"这个文件的冲突我已经处理完了"。你不需要额外告诉 Git "冲突已解决"——add 本身就是这个信号。
第四步:完成合并提交。
git commit -m "merge: 合并 dev 分支,解决 login.sh 冲突"Git 会生成一个特殊的合并提交(Merge Commit),它有两个父提交——一个来自 main,一个来自 dev。这次合并被永久记录在历史里。
不要慌
冲突是 Git 学习过程中的必经之路。它不是什么严重事故——恰恰相反,冲突说明你和你的队友(或者另一个平行宇宙里的你)都在努力写代码。Git 只是在需要人类判断的时候问了你一句,而不是自作主张替你决定。
手动解决冲突,是程序员的必修课。每解决一次,你就对代码的合并逻辑理解更深一层。
四、分支策略的演进
学会操作分支只是第一步。在一个团队里,"什么时候创建分支、什么时候合并、分支怎么命名"——这些规则统称为分支策略。不同的年代,主流策略也不同。
分支策略简史
Git Flow(2010,Vincent Driessen 提出)
Git Flow 是历史上第一套被广泛采用的分支模型。它定义了 5 种分支类型:main(正式发布)、develop(开发主线)、feature(功能分支)、release(发布准备)、hotfix(紧急修复)。每类分支都有严格的创建和合并规则。
它非常适合有明确版本发布周期的项目——比如游戏引擎、操作系统发行版。但对持续部署的 Web 应用来说,它的流程偏重:一个 hotfix 要从 main 拉分支,修完之后同时合并回 main 和 develop,步骤繁琐。
GitHub Flow(2011,GitHub 提出)
GitHub Flow 是对 Git Flow 的简化。它只有一条铁律:main 分支随时可以部署到生产环境。任何新功能从 main 拉分支,开发完成后提交 Pull Request,代码审查通过后合并回 main,立即部署。
它的核心理念是"部署即验证"——小步快跑,快速迭代。适合 SaaS 产品、创业团队和一切追求持续部署的项目。GitHub 自己就用这套流程。
Trunk-Based Development(主干开发)
这套策略更进一步:所有人都直接在 main(trunk)上提交,或者从 main 拉出存活不超过一两天的短分支。通过 feature flag(功能开关)控制新功能是否对用户可见,而不是通过分支来控制。
Google 和 Facebook 这种规模的公司在用——几千个工程师往同一个主干上提交,一天合并几十次。靠的是极其完善的自动化测试和 CI/CD 基础设施做兜底。如果你没有这些基础设施,别轻易模仿——否则 main 一天崩三次。
选哪个?
对于个人项目和学习阶段,GitHub Flow 足够了:从 main 拉分支,开发,合并,删除。等你进入团队且项目有明确的版本发布计划,再考虑 Git Flow。
五、master 还是 main?
如果你看到过 master 这个名字,它和 main 指的是同一个东西——主分支。只是名字变了。
一段技术与社会的交错
2020 年 6 月,GitHub 宣布将新建仓库的默认分支名从 master 改为 main。这个改动的直接原因是 Black Lives Matter 社会运动引发的对"master/slave"(主/从)术语体系的广泛反思——在技术领域,"master"这个词承载了一段并不光彩的历史。
Git 项目本身从 2.28 版本(2020 年 7 月)开始支持 init.defaultBranch 配置,允许用户自定义默认分支名。此后,GitLab、Bitbucket 等平台也陆续跟进。
对日常使用来说,这个改动几乎没有影响——该 checkout 的照样 checkout,该 merge 的照样 merge。只是你在 GitHub 上建新仓库时,默认看到的变成了 main。如果你还在用旧仓库,master 也依然能用,不会强制改名。
技术从来不是孤立于社会的——即便是一个分支名,也嵌在更大的历史背景里。
六、总结
git branch:查看所有本地分支,带*的是当前分支。git checkout -b <name>:创建并切换到新分支,进入隔离的开发环境。git checkout main:切回主线。看不到分支上的文件是正常的——它们被隔离在另一个分支里。git merge <branch>:把目标分支的修改合并到当前分支。如果主线没有分叉,Git 会用快进模式秒合并。git branch -d <branch>:删除已合并的分支。用-D可强制删除。- 冲突解决四步走:打开文件 → 删标记保留想要的代码 →
git add→git commit。
分支是 Git 最核心的竞争力。它让你可以大胆尝试、安全试错——搞砸了无非删掉分支重新来,主线永远安全。用好分支,你就从"写代码的人"升级为了"管理代码的人"。