没有分支的世界,混乱如麻
假设你在一个5人团队中开发一个电商项目。你们所有人都在 main 分支(之前叫 master)上直接改代码。这会发生什么?
- 张三正在开发"用户评价"功能,代码写了一半,突然被要求修复一个线上 bug
- 他没法把写了一半的代码提交(功能还没做完),也没法丢弃
- 他只能硬着头皮在混乱的代码上修 bug,结果把半成品功能也一起提交上去了
- 线上出现了一堆奇怪的报错,原因是张三的半成品代码被一起部署了
这就是没有分支的噩梦。分支就是来解决这个问题的——它让你可以在同一个项目中同时做多件事,互不干扰。
1. 分支是什么?一个生活化的理解
分支,就像你在玩一个角色扮演游戏:
graph LR
Main[主线剧情<br/>main分支] --> B1[你选择了拯救公主<br/>save-princess分支]
Main --> B2[你选择了拯救世界<br/>save-world分支]
B1 --> M1[两条线汇合<br/>merge回主线]
B2 --> M1
M1 --> Next[主线继续推进]
- 主线剧情 =
main分支,是项目的稳定版本 - 拯救公主 = 一个功能分支,你在上面开发新功能
- 拯救世界 = 另一个功能分支,你的同事在上面开发另一个功能
- 汇合 = 功能开发完了,合并回
main分支
每个分支都是一个独立的开发线,上面的修改不会影响其他分支。当你在"拯救公主"的剧情里死了无数次,也丝毫不会影响"拯救世界"的剧情线。
技术上的本质
从技术角度看,分支就是一个指向某个 commit 的指针。
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
commit id: "F"
在上图中:
main分支指向 commit Ffeature分支指向 commit D- commit A 和 B 是两个分支的共同祖先
Git 的分支非常轻量——创建一个分支只是创建了一个指针,几乎是瞬间完成的。这也是 Git 相比 SVN 的一大优势(SVN 的分支是完整拷贝,很慢)。
2. git branch —— 查看和管理分支
查看分支
# 查看本地所有分支
git branch
# 查看所有分支(包括远程分支)
git branch -a
# 查看远程分支
git branch -r
# 查看每个分支的最后一次提交
git branch -v
# 查看已合并到当前分支的分支
git branch --merged
# 查看尚未合并到当前分支的分支
git branch --no-merged
输出示例:
* main
feature/login
feature/payment
fix/header-bug
*号标记的是当前所在分支- 绿色的是本地分支
- 红色的是远程分支(
git branch -a时会显示,如remotes/origin/main)
创建分支
# 创建分支(但不会自动切换过去)
git branch feature/new-feature
# 创建分支并立刻切换到新分支(最常用)
git checkout -b feature/new-feature
# 或者用新的 switch 命令(Git 2.23+)
git switch -c feature/new-feature
删除分支
# 删除本地分支(已合并的)
git branch -d feature/old-feature
# 强制删除本地分支(即使没合并)
git branch -D feature/old-feature
# 删除远程分支
git push origin --delete feature/old-feature
# 或者简写
git push origin :feature/old-feature
重命名分支
# 重命名当前分支
git branch -m new-branch-name
# 重命名指定分支
git branch -m old-name new-name
3. git checkout 和 git switch —— 切换分支
git checkout(传统命令)
# 切换到已有分支
git checkout main
# 创建并切换到新分支
git checkout -b feature/new-feature
git checkout 是一个功能过载的命令——它既可以切换分支,又可以恢复文件(git checkout -- file),还可以创建分支。一个命令做太多事情,容易让人困惑。
为了解决这个问题,Git 2.23 版本引入了两个新命令:
git switch:专门用来切换分支git restore:专门用来恢复文件
git switch(推荐使用,更清晰)
# 切换到已有分支
git switch main
# 创建并切换到新分支
git switch -c feature/new-feature
# 切换到上一个分支(超实用!)
git switch -
git switch - 是一个极其方便的命令。比如你在 feature/login 分支开发,临时需要切到 main 看个东西,然后马上切回来:
# 当前在 feature/login
git switch main # 切到 main
# 看完东西了
git switch - # 一键切回 feature/login
切换分支时工作区的影响
切换分支时,Git 会把你工作区的文件变成目标分支的状态。这有一个前提:你的工作区必须是干净的(没有未提交的修改)。
如果有未提交的修改,Git 会拒绝切换:
error: Your local changes to the following files would be overwritten by checkout:
src/app.js
Please commit your changes or stash them before you switch branches.
解决方案有三种:
- 提交:
git add . && git commit -m "xxx" - 丢弃:
git restore .(谨慎!) - 暂存:
git stash(见下一节)
4. git stash —— 暂存你的工作
git stash 是 Git 中最实用的"救急"命令之一。它的作用是:把你当前工作区中所有未提交的修改"藏"起来,让工作区变干净。
使用场景
你正在开发一个新功能,代码写了一半,还没到能提交的地步。这时组长突然说:"线上有个紧急 bug,你赶快修一下。"
你怎么办?
- 把写了一半的代码提交?不行,功能还没做完,提交上去不完整
- 把代码丢弃?不行,写了半天白写了
- 用
git stash!
graph LR
A[正在开发新功能<br/>代码未完成] -->|git stash| B[工作区变干净<br/>可以切分支修bug]
B -->|修完bug切回来| C[git stash pop<br/>恢复之前的工作]
C --> D[继续开发新功能]
基本用法
# 暂存当前所有修改
git stash
# 暂存时添加描述信息(推荐,方便后面查找)
git stash save "正在开发登录功能,保存进度"
# 查看所有暂存列表
git stash list
# 恢复最近一次暂存的修改(同时删除 stash 记录)
git stash pop
# 恢复最近一次暂存的修改(保留 stash 记录)
git stash apply
# 恢复指定的暂存
git stash pop stash@{1}
git stash apply stash@{1}
# 删除最近一次暂存
git stash drop
# 删除所有暂存
git stash clear
# 查看某次暂存的具体内容
git stash show -p stash@{0}
stash 的更多用法
# 暂存所有修改(包括未跟踪的新文件)
git stash -u
# 或
git stash --include-untracked
# 暂存所有修改(包括未跟踪和被忽略的文件)
git stash -a
# 或
git stash --all
# 暂存时保留暂存区(即 git add 过的文件保持不变)
git stash --keep-index
# 从 stash 中创建一个新分支
git stash branch new-branch-name stash@{0}
git stash branch 是一个特别实用的命令:当你恢复 stash 时遇到冲突,可以用这个命令创建一个新分支来解决冲突。
stash 列表解读
stash@{0}: WIP on feature/login: abc1234 添加登录表单验证
stash@{1}: WIP on main: def5678 修复首页样式
stash@{0}:索引,0 是最新的WIP:Work In Progress,进行中的工作on feature/login:当时所在的分支abc1234:当时所在 commit 的哈希值
5. git merge —— 合并分支
当你在功能分支上开发完毕,就要把代码合并回 main 分支。这就是 git merge。
基本用法
# 1. 先切换到目标分支(你要把代码合并到哪里)
git switch main
# 2. 合并源分支(把谁的代码合并进来)
git merge feature/login
这个操作的意思是:把 feature/login 分支的修改合并到当前所在的 main 分支。
两种合并方式:Fast-Forward 和 三方合并
Fast-Forward(快进合并)
当目标分支没有新提交时,Git 会使用 Fast-Forward 模式:
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
合并前:main 在 B,feature 在 D。main 上没有 feature 不知道的新提交。
gitGraph
commit id: "A"
commit id: "B"
commit id: "C"
commit id: "D"
合并后:main 指针直接"快进"到 D。没有产生新的合并提交,历史是一条直线。
Fast-Forward 合并的条件:
- 目标分支(main)在源分支(feature)创建后没有新提交
- 两个分支的历史是线性的
三方合并(3-Way Merge)
当目标分支和源分支都有新提交时,Git 需要进行三方合并:
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout main
commit id: "E"
checkout feature
commit id: "C"
commit id: "D"
合并前:main 有 E,feature 有 C 和 D。两个分支都从 B 分叉了。
gitGraph
commit id: "A"
commit id: "B"
commit id: "E"
branch feature
commit id: "C"
commit id: "D"
checkout main
merge feature
commit id: "M"
合并后:Git 创建了一个新的"合并提交"M,它有两个父提交——E 和 D。Git 会自动比较 A(共同祖先)、E(main 的版本)、D(feature 的版本)三者的差异,进行三方合并。
合并参数
# 禁用 Fast-Forward,强制创建合并提交
git merge --no-ff feature/login
# 仅当可以 Fast-Forward 时才合并(如果不能则拒绝)
git merge --ff-only feature/login
# 合并时添加自定义的合并提交信息
git merge -m "合并登录功能" feature/login
# 合并但不自动提交(让你检查后再手动提交)
git merge --no-commit feature/login
# 放弃合并(合并过程中遇到冲突想取消时使用)
git merge --abort
Fast-Forward vs --no-ff:该选哪个?
| Fast-Forward | --no-ff | |
|---|---|---|
| 历史记录 | 一条直线,看不出分支的存在 | 保留分支痕迹,清楚看到哪个功能何时合并 |
| 合并提交 | 不产生 | 产生一个 merge commit |
| 回退方便性 | 较难精确回退整个功能 | 可以一键 revert 整个功能的合并 |
| 适用场景 | 个人项目 | 团队协作项目 |
建议:团队协作项目中,推荐使用
--no-ff。虽然会多一个合并提交,但历史记录更清晰,出问题时更容易定位和回退。
6. 分支策略 —— 团队是怎么用分支的?
不同的团队有不同的分支策略。面试中面试官可能会问:"你们公司用的是什么分支策略?"你需要知道常见的几种。
Git Flow
最经典的分支策略,适合有固定发布周期的项目。
gitGraph
commit id: "v1.0"
branch develop
checkout develop
commit id: "开发中"
branch feature/login
checkout feature/login
commit id: "登录开发"
commit id: "登录完成"
checkout develop
merge feature/login
branch feature/payment
checkout feature/payment
commit id: "支付开发"
commit id: "支付完成"
checkout develop
merge feature/payment
branch release/1.1
checkout release/1.1
commit id: "版本准备"
checkout main
merge release/1.1
checkout develop
merge release/1.1
两种长期分支:
main(或master):生产环境代码,只接受从 release 和 hotfix 分支的合并develop:开发主分支,功能分支从这里分出,也合并回这里
三种临时分支:
feature/*:功能分支,从 develop 分出,开发完合并回 developrelease/*:发布分支,从 develop 分出,准备发布时创建。在这个分支上只做 bug 修复和版本号修改hotfix/*:紧急修复分支,从 main 分出,修复线上紧急 bug。修复后同时合并回 main 和 develop
Git Flow 的优缺点:
| 优点 | 缺点 |
|---|---|
| 结构清晰,分工明确 | 分支太多,流程复杂 |
| 适合大型项目 | 不适合持续部署(CI/CD) |
| 版本管理规范 | 小型团队会觉得太重 |
GitHub Flow
更简洁的分支策略,适合持续部署的 Web 项目。
gitGraph
commit id: "初始版本"
branch feature/login
checkout feature/login
commit id: "登录开发"
commit id: "CR修改"
checkout main
merge feature/login
branch feature/payment
checkout feature/payment
commit id: "支付开发"
checkout main
merge feature/payment
只有一个长期分支:
main:始终保持可部署状态
规则:
main分支上的任何东西都可以部署- 开发新功能时,从
main创建描述性的分支(如feature/login) - 在本地分支上开发,定期 push 到远程
- 需要帮助或代码审查时,开 Pull Request
- PR 审核通过后,合并到
main - 合并后立即部署
GitHub Flow 的优缺点:
| 优点 | 缺点 |
|---|---|
| 简单,新手友好 | 对版本管理不太友好 |
| 适合持续部署 | 多人同时合并容易冲突 |
| PR 驱动开发 | 缺少发布缓冲 |
GitLab Flow
结合了 Git Flow 和 GitHub Flow 的优点,增加了环境分支(如 staging、production)。
国内互联网公司常见的做法
很多国内公司不会严格遵守某一种分支策略,而是采用简化版本:
main:线上代码develop或dev:开发主分支feature/xxx:功能分支- 直接从
feature/*提 PR 到develop - 发布时从
develop合并到main
7. 分支命名规范
好的命名规范让团队协作更顺畅:
| 前缀 | 用途 | 示例 |
|---|---|---|
feature/ | 新功能开发 | feature/user-login、feature/暗黑模式 |
fix/ 或 bugfix/ | Bug 修复 | fix/login-error、bugfix/首页白屏 |
hotfix/ | 紧急线上修复 | hotfix/payment-crash |
release/ | 发布准备 | release/v2.0.0 |
refactor/ | 代码重构 | refactor/api-layer |
docs/ | 文档更新 | docs/api-guide |
chore/ | 杂项(依赖更新等) | chore/update-deps |
命名建议:
- 使用小写字母和连字符(
-) - 简短但有描述性
- 可以包含日期或 issue 编号:
feature/LOGIN-2026
8. 动手实战:分支操作全流程
让我们用一个完整的例子,把上面学到的所有命令串起来。
场景模拟
你接到任务:给电商项目添加"商品搜索"功能。同时,你还有上篇文章中的"按钮样式修复"需要继续。
实战步骤
# ============ 第一步:查看当前状态 ============
git status
git branch
# 确认在 main 分支,确认工作区干净
# ============ 第二步:拉取最新代码 ============
git pull
# ============ 第三步:创建功能分支 ============
git switch -c feature/product-search
# 或 git checkout -b feature/product-search
# ============ 第四步:在分支上开发 ============
# ... 写代码中 ...
# 创建了 search.js,修改了 index.html
# ============ 第五步:查看修改 ============
git status
# On branch feature/product-search
# Changes not staged for commit:
# modified: index.html
# Untracked files:
# search.js
git diff
# 查看具体改了什么
# ============ 第六步:提交代码 ============
git add index.html search.js
git commit -m "feat(search): 添加商品搜索功能
实现了基于关键字的商品搜索,支持模糊匹配。"
# ============ 第七步:紧急插播——线上出bug了! ============
# 搜索功能还没做完,不能提交
git stash save "搜索功能开发中-暂存进度"
# 切回 main 分支
git switch main
# 拉取最新代码
git pull
# 创建修复分支
git switch -c hotfix/search-crash
# 修复 bug...
git add .
git commit -m "fix(search): 修复搜索时输入特殊字符导致的崩溃"
# 推送到远程
git push -u origin hotfix/search-crash
# 切回 main,合并修复
git switch main
git merge --no-ff hotfix/search-crash
git push
# ============ 第八步:回到搜索功能开发 ============
git switch feature/product-search
# 恢复之前的工作进度
git stash pop
# 继续开发搜索功能...
git add .
git commit -m "feat(search): 完成搜索结果分页功能"
# ============ 第九步:推送功能分支 ============
git push -u origin feature/product-search
# ============ 第十步:切回 main,合并功能 ============
git switch main
git pull
git merge --no-ff feature/product-search
git push
# ============ 第十一步:清理已合并的分支 ============
git branch -d feature/product-search
git branch -d hotfix/search-crash
git push origin --delete feature/product-search
git push origin --delete hotfix/search-crash
9. 分支相关的常见错误和补救
不小心在错误的分支上改了代码
# 情况:你在 main 分支上改了代码,但应该在 feature/login 分支上改
# 方案1:用 stash 搬运(推荐)
git stash # 暂存修改
git switch feature/login # 切到正确分支
git stash pop # 恢复修改
# 方案2:先提交,再 cherry-pick(第六篇会详细讲)
git add .
git commit -m "临时提交"
# 记下 commit 哈希值
git switch feature/login
git cherry-pick <commit-hash>
git switch main
git reset --hard HEAD~1 # 删除 main 上的那个提交
分支合并完忘了删除,越来越多
# 查看哪些分支已经合并了
git branch --merged
# 批量删除已合并的本地分支(保留 main 和 develop)
git branch --merged | grep -v "main\|develop" | xargs git branch -d
本篇小结
分支管理是 Git 中最核心也最强大的功能。掌握了分支,你就从"单机模式"进入了"多人协作模式"。
- ✅ 理解了分支的本质:指向 commit 的指针
- ✅ 学会了分支的创建、切换、删除
- ✅ 掌握了
git stash暂存工作进度 - ✅ 理解了
git merge的两种模式 - ✅ 了解了 Git Flow、GitHub Flow 等分支策略
- ✅ 完成了完整的实战演练
但分支合并还有一个重要的话题我们留到下一篇——冲突。当两个人修改了同一个文件的同一行代码时,Git 无法自动判断谁对谁错,就会产生冲突。这是新手最怕的情况之一,但其实只要理解了原理,冲突一点都不可怕。
下一篇,我们将专门讲解冲突解决和版本回退,教你如何从容应对各种"翻车"场景。我们下一篇见!