编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 质量保障

  • 产品思考

  • 软实力

  • 开发流程

  • Git应用

    • README
    • 版本控制的诞生:从一场灾难说起
    • 单人工作流:在 Git 的时光机里自由穿梭
    • 分支:Git 的灵魂——像开平行宇宙一样开发
      • 一、两线作战
      • 二、分支本质
        • 2.1 哲学问题
        • 2.2 四十字节指针
        • 2.3 时间岔路
      • 三、日常操作
        • 3.1 创建切换
        • 3.2 切换须知
        • 3.3 删除分支
        • 3.4 命名惯例
      • 四、合并分支
        • 先准备实验环境
        • 4.1 快进合并
        • 4.2 三方合并
        • 4.3 推荐操作
      • 五、合并冲突
        • 5.1 冲突原因
        • 5.2 制造冲突
        • 5.3 冲突模样
        • 5.4 解决策略
        • 5.5 完成合并
        • 5.6 放弃合并
      • 六、变基操作
        • 6.1 历史混乱
        • 6.2 工作原理
        • 6.3 黄金法则
      • 七、交互变基
        • 7.1 整理分支
        • 7.2 四种操作
        • 7.3 操作速查表
      • 八、终极对比
      • 九、综合实战
        • 场景设定
        • 实战开始
        • 时间线回顾
      • 十、本章回顾
        • 📎 本章涉及的命令速查
    • 远程协作:把你的代码推到全世界
    • Git特种作战:stash、cherry-pick、bisect三件套
    • 团队工作流实战:从一个人能打到一队人能战
    • Git故障排除:遇到报错不再慌的急诊手册
    • Git 场景速查地图:遇到问题对号入座
    • 常见操作实践:从理论到实战的最后一步
  • 技术模版

  • 技术规范

  • markdown

  • mermaid

  • license

  • 博客部署

  • 技术招聘

  • 测试经验

  • 技术
  • Git应用
杨充
2025-06-06
目录

分支:Git 的灵魂——像开平行宇宙一样开发

# 第3章 · 分支本质

# 一、两线作战

下午 3 点,小李正在做一个「用户积分系统」。他把代码改得满目疮痍——user.js 改了 200 行,points.js 是全新的文件,数据库表结构也重构了一半。还没 commit。现在整个项目处于一种「拆了一半、跑不起来」的状态。

突然,Leader 在群里 @ 他:

"线上登录崩了,所有用户都登不进去。紧急!你 15 分钟内搞定。"

小李愣住了。他的大脑飞速运转:

  1. 先把现在的改动 commit 了? → 不行,代码跑不起来,commit 一个 broken commit 太丢人。
  2. 把现在的改动全丢掉? → 不行,改了一下午,丢了等于白干。
  3. 把现在的项目复制一份? → 两个目录来回切?上次这么干,复制了 3GB 的 node_modules,硬盘差点冒烟。

他卡住了。手里正在做的功能不能丢,线上 bug 必须要修。两条时间线在同一个工作区里打架。

这不是小李一个人的困境。 任何一个程序员,只要同时做两件事,都会遇到这个问题。好消息是:Git 从设计的第一天起,就把这个问题变成了它的核心卖点。


# 二、分支本质

# 2.1 哲学问题

你正在读一本书的第 100 页。现在你想做一个实验:

「如果第 50 页的主角做了不同的选择,后面的故事会怎么发展?」

你怎么办?你不会撕了后半本重新写。你会在第 50 页夹一个书签,然后拿一本空白的笔记本,从第 50 页开始「写自己的版本」。想回到原故事线?把笔记本合上,翻到第 100 页继续读。

这个书签,就是 Git 里的分支。 它只是一个「你现在读到哪一页了」的标记。

# 2.2 四十字节指针

打开终端,亲眼看看:

mkdir branch-lab && cd branch-lab
git init

# 创建第一个 commit
echo "第1章" > story.txt
git add . && git commit -m "第1章"

echo "第2章" >> story.txt
git add . && git commit -m "第2章"

# 分支到底是什么?
cat .git/refs/heads/master
# 输出类似:8f3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a
#           ↑ 就是一个 40 字符的 hash!
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这就是分支的全部内容:一个写着 commit hash 的文件。 创建分支只是写一个 40 字符的文件——毫秒级。这也是为什么 Git 的分支既快又廉价。

.git/refs/heads/
  ├── master   →  "8f3a2b1c4d5e..."
  └── dev      →  "7e2d1c0b9a8f..."
1
2
3

现在你理解了第 1 章那句话:「分支只是一个可以移动的指针。」

# 2.3 时间岔路

# 当前历史:
#   a1b2c3d 第2章  ← master 指向这里
#   b1c2d3e 第1章

# 创建新分支:就像在第1章和第2章之间放了一个分岔路口
git branch feature/魔法世界

# 看看分支列表
git branch
# 输出:
#   feature/魔法世界
# * master              ← * 表示你当前在哪个分支

# 切换到新分支
git switch feature/魔法世界
# 或 git checkout feature/魔法世界(旧版命令)

git log --oneline
#   a1b2c3d 第2章  ← 两个分支目前指向同一个 commit
#   b1c2d3e 第1章

# 在新分支上写内容
echo "第3章(魔法世界线):主角获得了魔法" >> story.txt
git add . && git commit -m "第3章:发现魔法"

# 切回 master
git switch master

# 在 master 上写完全不同的内容
echo "第3章(现实世界线):主角考上了大学" >> story.txt
git add . && git commit -m "第3章:考上大学"

# 现在看全貌
git log --oneline --graph --all
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

输出:

* ccccccc 第3章:考上大学                      ← master
| * ddddddd 第3章:发现魔法                     ← feature/魔法世界
|/
* a1b2c3d 第2章                                 ← 分岔点
* b1c2d3e 第1章
1
2
3
4
5

同一天下午 3 点,你的项目分裂成了两个平行宇宙。 一个世界里主角考上了大学,另一个世界里主角成为了魔法师。而这一切,只花了几个 40 字节的指针。


# 三、日常操作

# 3.1 创建切换

git switch -c feature/用户积分    # 创建 + 切换(推荐)
# 等价旧命令:git checkout -b feature/用户积分
1
2

# 3.2 切换须知

# 切换前,工作区必须是干净的(或者用 stash,第 6 章讲)
git status
# 如果有未 commit 的修改,Git 会阻止切换

# 查看所有分支(带最新 commit)
git branch -v

# 查看哪些分支已经合并到当前分支
git branch --merged
# 这些可以安全删除

# 查看尚未合并的分支
git branch --no-merged
# 删之前想一想
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.3 删除分支

git branch -d feature/魔法世界    # 安全删除(已合并的)
git branch -D feature/魔法世界    # 强制删除(没合并也删)
1
2

# 3.4 命名惯例

好的文件名让人一眼看懂,好的分支名也一样:

feature/用户积分          # 新功能
hotfix/登录崩溃           # 紧急修复
release/v1.2.0           # 发布准备
bugfix/分页失效           # bug 修复
experiment/新算法测试     # 实验性分支
1
2
3
4
5

# 四、合并分支

分支分开了,总得合回来。Git 合并有两种截然不同的方式。

# 先准备实验环境

mkdir merge-lab && cd merge-lab && git init

echo "const app = (() => {" > app.js
echo "  return { name: 'My App', version: '1.0' };" >> app.js
echo "})();" >> app.js
git add . && git commit -m "init: 应用骨架"

# 从 master 开出 feature 分支
git switch -c feature/添加问候

echo "function greet() { return 'Hello!'; }" >> app.js
git add . && git commit -m "feat: 添加问候函数"

# 切回 master,做点别的
git switch master
echo "function version() { return '1.0'; }" >> app.js
git add . && git commit -m "feat: 添加版本函数"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

现在的历史:

* eeeeeee feat: 添加版本函数          ← master
| * fffffff feat: 添加问候函数         ← feature/添加问候
|/
* aaaaaaa init: 应用骨架
1
2
3
4

# 4.1 快进合并

当 master 没有自己的新 commit 时(即 master 直接是 feature 的祖先),Git 只需要把 master 指针「快进」到 feature 的位置。

# 先模拟一个"纯净"场景:master 没有新提交,feature 有
git switch master
git reset --hard aaaaaaa   # 退回到 init

git switch -c feature/简单功能
echo "const DEBUG = true;" >> app.js
git add . && git commit -m "feat: 添加调试开关"

git switch master
git merge feature/简单功能

# 输出:Fast-forward
# 没有新的 merge commit!master 直接移到了 feature 的位置
1
2
3
4
5
6
7
8
9
10
11
12
13

Fast-forward 的结果:历史是一条直线,没有合并痕迹。 干净,但你看不出「这里曾经有个分支」。

# 4.2 三方合并

回到之前的场景——master 和 feature 各自有独立的提交:

git switch master
git merge feature/添加问候
1
2

这次 Git 会弹出一个编辑器让你写 merge commit message。因为它不能简单地「快进」——master 和 feature 分叉后各自前行了,Git 需要把两个分支的改动综合起来。

git log --oneline --graph

# 输出:
# *   ggggggg Merge branch 'feature/添加问候'   ← 新的合并 commit
# |\
# | * fffffff feat: 添加问候函数
# * | eeeeeee feat: 添加版本函数
# |/
# * aaaaaaa init: 应用骨架
1
2
3
4
5
6
7
8
9

这个结果比 Fast-forward 多了一个「合并提交」。它完整记录了:branch 什么时候创建的、在它上面做了什么、什么时候合回来的。

# 4.3 推荐操作

git merge --no-ff feature/简单功能
1

即使可以快进,也强制生成一个 merge commit。好处:以后看历史,一眼就知道「这里曾经有个 feature 分支」。团队协作强烈推荐。


# 五、合并冲突

# 5.1 冲突原因

冲突的本质很简单:Git 很聪明,但没那么聪明。

  • ✅ Git 能自动合并:两个分支改了不同的文件,或同一文件的不同行。
  • ❌ Git 不能自动合并:两个分支改了同一文件的同一行。Git 不知道该保留谁的,只能举手投降:"你来决定。"

# 5.2 制造冲突

mkdir conflict-lab && cd conflict-lab && git init

echo "let config = {" > config.js
echo "  theme: 'light'," >> config.js
echo "  lang: 'zh'" >> config.js
echo "};" >> config.js
git add . && git commit -m "init: 基础配置"

# 创建两个分支,各自改同一行
git switch -c feature/暗黑模式
# 把 theme 改成 dark
sed -i '' "s/theme: 'light'/theme: 'dark'/" config.js
git add . && git commit -m "feat: 默认暗黑模式"

git switch master
# master 上把 theme 改成 auto
sed -i '' "s/theme: 'light'/theme: 'auto'/" config.js
git add . && git commit -m "feat: 自适应主题"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

现在试试合并:

git merge feature/暗黑模式
1

输出:

Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.
1
2
3

别慌! 这不是错误,这只是 Git 在说「你来决定」。

# 5.3 冲突模样

cat config.js
1
let config = {
<<<<<<< HEAD
  theme: 'auto',          ← master 的版本(HEAD 是你当前所在分支)
=======
  theme: 'dark',          ← feature/暗黑模式 的版本
>>>>>>> feature/暗黑模式
  lang: 'zh'
};
1
2
3
4
5
6
7
8

解读:

  • <<<<<<< HEAD 到 ======= 之间:当前分支(master)的内容
  • ======= 到 >>>>>>> feature/暗黑模式 之间:被合并分支的内容
  • 你的任务:把这段混乱的标记,改成一个"综合了两边优点"的最终版本。

# 5.4 解决策略

策略 A:保留 master 的

# 手动编辑 config.js,删掉冲突标记,只保留 theme: 'auto'
1

编辑 config.js:

let config = {
  theme: 'auto',
  lang: 'zh'
};
1
2
3
4

策略 B:保留 feature 的

let config = {
  theme: 'dark',
  lang: 'zh'
};
1
2
3
4

策略 C:融合两边的(最常用的真实场景)

let config = {
  theme: 'auto',       // 默认自动检测,但我们给暗黑模式选项
  darkMode: true,      // 新增暗黑模式开关
  lang: 'zh'
};
1
2
3
4
5

# 5.5 完成合并

# 解决完冲突后
git add config.js           # 标记「冲突已解决」
git status
# 输出:
# All conflicts fixed but you are still merging.
#   (use "git commit" to conclude merge)

git commit -m "merge: 合并暗黑模式,保留自适应主题并添加暗黑开关"
# 冲突解决完成!
1
2
3
4
5
6
7
8
9

# 5.6 放弃合并

git merge --abort
# 一键回到合并前的干净状态。毫无痕迹,就像没操作过。
1
2

💡 心态转变:不要怕冲突。冲突不是 Git 的 bug,是 Git 的诚实——它在告诉你「这件事需要人工判断」。害怕冲突 → 不分支 → 所有代码混在一条线上 → 更混乱。拥抱冲突 → 频繁分支 → 每次合并都小且可控。


# 六、变基操作

# 6.1 历史混乱

想象你维护一个功能分支两周,每天都有别人往 master 合新代码:

*   merge #14
|\
| * feature commit #3
* | master commit #14
| * feature commit #2
* | master commit #13
| * feature commit #1
|/
1
2
3
4
5
6
7
8

三个月后,没人能看懂这张蜘蛛网。rebase 的使命就是把这个蜘蛛网捋成一条直线。

# 6.2 工作原理

rebase 的本质:把你分支上的 commit "搬"到目标分支的最新 commit 之后,然后一个个重新应用。

mkdir rebase-lab && cd rebase-lab && git init

echo "第一版" > code.txt
git add . && git commit -m "v1"

git switch -c feature

echo "功能A" >> code.txt
git add . && git commit -m "feat: A"

echo "功能B" >> code.txt
git add . && git commit -m "feat: B"

git switch master
# 模拟别人在 master 上推了新代码
echo "线上修复" >> code.txt
git add . && git commit -m "hotfix: 紧急修复"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

当前历史(使用 git log --oneline --graph --all):

* hhhhhhh hotfix: 紧急修复              ← master
| * ggggggg feat: B                     ← feature
| * fffffff feat: A
|/
* aaaaaaa v1
1
2
3
4
5

开始 rebase:

git switch feature
git rebase master
1
2

Git 做的三件事:

  1. 🚚 把 feature 上的 A 和 B 「拆下来」(暂时保存)
  2. 📍 把 feature 的指针移到 master 的最新点
  3. 📥 把 A 和 B 按照顺序「重新安装」上去

结果:

* ggggggg' feat: B                      ← feature(重新应用,hash 变了)
* fffffff' feat: A                      ← 重新应用
* hhhhhhh hotfix: 紧急修复              ← master
* aaaaaaa v1
1
2
3
4

历史变成了一条直线——和线性开发一模一样,看不出曾经分叉过。

# 6.3 黄金法则

绝不对已经 push 到公共仓库的 commit 执行 rebase。

原因很简单:rebase 会改写 commit hash。如果你改了已经 push 的 commit,别人的本地仓库就会和你产生不可调和的冲突。团队协作中,破坏公共历史的代价远大于历史不整洁的代价。

✅ 私有分支上 rebase → 整理好再 push     → 完美
❌ 公共分支上 rebase → 别人的仓库全乱    → 灾难
1
2

# 七、交互变基

你做了一个 feature,提交了 5 次——这 5 次里有「修 typo」「加注释」「真正功能」「临时 debug」「去掉 debug」。推给团队的时候,应该是一个干净、专业的提交列表,而不是你的草稿本。

# 7.1 整理分支

mkdir rebase-i-lab && cd rebase-i-lab && git init

echo "const user = {};" > user.js
git add . && git commit -m "init"

# 模拟混乱的开发过程
echo "function login() {}" >> user.js
git add . && git commit -m "开始写登录"

echo "function logout() {}" >> user.js
git add . && git commit -m "加一个登出 忘了写"

# 写错了
echo "asdfasdf" >> user.js
git add . && git commit -m "tmp: debug日志 等下删"

# 修复
# 删除 debug 日志
sed -i '' '/asdfasdf/d' user.js
git add . && git commit -m "修一下"

echo "function validate() {}" >> user.js
git add . && git commit -m "feat: 添加输入校验"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

现在的历史:

e9e9e9e feat: 添加输入校验
d8d8d8d 修一下
c7c7c7c tmp: debug日志 等下删     ← 不该存在
b6b6b6b 加一个登出 忘了写           ← message 太随意
a5a5a5a 开始写登录
0000000 init
1
2
3
4
5
6

用交互式 rebase 整理:

git rebase -i HEAD~5
1

弹出编辑器:

pick a5a5a5a 开始写登录
pick b6b6b6b 加一个登出 忘了写
pick c7c7c7c tmp: debug日志 等下删
pick d8d8d8d 修一下
pick e9e9e9e feat: 添加输入校验
1
2
3
4
5

# 7.2 四种操作

修改为:

pick a5a5a5a 开始写登录
squash b6b6b6b 加一个登出 忘了写    ← squash:合并到上一个 commit
drop c7c7c7c tmp: debug日志 等下删   ← drop:直接删除
squash d8d8d8d 修一下               ← 也合并进去
reword e9e9e9e feat: 添加输入校验    ← reword:改 message
1
2
3
4
5

保存退出后,Git 会让你编辑合并后的 commit message:

# 第一个合并后的 commit
feat: 实现用户登录和登出功能     ← 新的、专业的 message

# 第二个
feat: 添加输入校验
1
2
3
4
5

最终结果:

git log --oneline
# 输出:
# xxxxxxx feat: 添加输入校验
# yyyyyyy feat: 实现用户登录和登出功能
# 0000000 init
1
2
3
4
5

5 个凌乱的 commit → 2 个干净的 commit。推给团队时,就像你一次性就写对了。

# 7.3 操作速查表

操作 含义 什么时候用
pick 保留这个 commit 不变 默认
reword 改 commit message message 写得不好
squash 合并到上一个 commit,保留 message 把多个小 commit 合成一个
fixup 合并到上一个 commit,丢弃 message 把「修 typo」合并到「真正功能」
drop 删除这个 commit 临时 debug commit 删掉
edit 停下来让你手动改这个 commit 的内容 只想改其中几个文件

# 八、终极对比

维度 merge rebase
历史形态 保留真实分叉记录 线性历史,看起来像没分过叉
commit hash 不变 重新生成(变了)
冲突解决方式 一次性解决全部冲突 每个 commit 依次解决冲突
适合场景 公共分支合并、PR 合并 整理私有分支、同步上游
可追溯性 保留了「何时分支的」 丢失了分支信息
安全性 安全 遵循黄金法则才安全

一句话建议:日常往团队分支合代码用 merge,整理自己的分支历史用 rebase。


# 九、综合实战

# 场景设定

你是小李,公司电商平台要开发「优惠券系统」。同时,线上报了一个紧急 bug:商品详情页的图片全部 404。

# 实战开始

# ==================== Phase 1:初始化项目 ====================
mkdir ecommerce && cd ecommerce && git init

echo 'const app = {
  name: "电商平台",
  version: "1.0.0"
};' > app.js

echo 'const products = [
  { id: 1, name: "iPhone", price: 6999 },
  { id: 2, name: "MacBook", price: 12999 }
];' > products.js

echo 'function getProductImage(id) {
  return `/images/products/${id}.jpg`;
}' > images.js

git add . && git commit -m "init: 电商平台基础骨架"


# ==================== Phase 2:开始开发优惠券功能 ====================
git switch -c feature/优惠券系统

echo '
// ===== 优惠券模块 =====
const coupons = [];

function createCoupon(code, discount, minAmount) {
  coupons.push({ code, discount, minAmount });
  return coupons[coupons.length - 1];
}

function applyCoupon(code, orderAmount) {
  const coupon = coupons.find(c => c.code === code);
  if (!coupon) return { valid: false, reason: "优惠券不存在" };
  if (orderAmount < coupon.minAmount) {
    return { valid: false, reason: `订单金额需满${coupon.minAmount}` };
  }
  return { valid: true, discountedAmount: orderAmount - coupon.discount };
}
' > coupon.js

git add . && git commit -m "feat: 优惠券创建与应用逻辑"

# 继续开发...
echo '
function listCoupons() {
  return coupons.map(c => ({
    ...c,
    status: c.expired ? "已过期" : "可用"
  }));
}
' >> coupon.js

git add . && git commit -m "feat: 优惠券列表查询"

# 还没开发完,优惠券的过期判断和前端页面都还没写
# 代码处于"半成品"状态


# ==================== Phase 3:紧急!线上图片 404 ====================
# Leader 紧急通知:所有商品图片 404!
# 小李检查 images.js:

cat images.js
# 发现路径写错了——应该是 /static/images/products/ 而不是 /images/products/

# 但你现在在 feature 分支上,优惠券写到一半,不能提交...

# 🎯 第一步:保存现场
git stash                    # 暂时保存工作现场(第 6 章细讲)
git status                   # 确认:working tree clean

# 🎯 第二步:切到 master,创建 hotfix 分支
git switch master
git switch -c hotfix/图片路径修复

# 🎯 第三步:修复 bug
# 把路径修正
sed -i '' 's|/images/products/|/static/images/products/|g' images.js
cat images.js  # 确认修改

git add images.js
git commit -m "hotfix: 修复商品图片路径 404 问题"

# 🎯 第四步:合回 master
git switch master
git merge --no-ff hotfix/图片路径修复 -m "merge: 合并图片路径 hotfix"

# 发布!线上恢复了!
git log --oneline --graph
# 输出:
# *   merge: 合并图片路径 hotfix
# |\
# | * hotfix: 修复商品图片路径 404 问题
# |/
# * init: 电商平台基础骨架


# ==================== Phase 4:回到优惠券开发 ====================
git switch feature/优惠券系统
git stash pop                 # 恢复现场!

# 继续开发...
echo '
function checkExpiry(coupon) {
  return new Date() > new Date(coupon.expireDate);
}
' >> coupon.js

git add . && git commit -m "feat: 优惠券过期校验"


# ==================== Phase 5:同步 master 的最新代码(含 hotfix) ====================
# 你开发了两天,master 上已经合了那个 hotfix
# 需要把 master 的修复同步到你这边

git merge master

# 如果没有冲突(优惠券和图片路径是不同文件),直接成功
# 如果有冲突 → 按照 5.3 节的方法解决

git log --oneline --graph
# 输出:
# *   merge: 合并 master 到 feature/优惠券系统
# |\
# | *   merge: 合并图片路径 hotfix         ← master
# | |\
# | | * hotfix: 修复商品图片路径 404 问题
# | |/
# | * init: 电商平台基础骨架
# * | feat: 优惠券过期校验                  ← feature
# * | feat: 优惠券列表查询
# * | feat: 优惠券创建与应用逻辑
# |/
# * init: 电商平台基础骨架


# ==================== Phase 6:整理 feature 分支的 commit ====================
# 推给团队之前,把 3 个 commit 整理成 1 个

git rebase -i HEAD~3

# 把后两个改成 squash:
# pick aaaaaaa feat: 优惠券创建与应用逻辑
# squash bbbbbbb feat: 优惠券列表查询
# squash ccccccc feat: 优惠券过期校验

git log --oneline
# 输出:
# xxxxxxx feat: 实现完整优惠券系统(创建、查询、过期校验)
# ...


# ==================== Phase 7:提 PR,合并到 master ====================
git push origin feature/优惠券系统
# 去 GitHub 提 Pull Request → Code Review → Merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

# 时间线回顾

        3:00 PM              3:05 PM              3:20 PM              3:30 PM
    ──────┼──────────────────┼────────────────────┼────────────────────┼──────→
           │                  │                    │                    │
    接到线上 bug 通知     git stash 保存现场    切 hotfix 修 bug     bug 修复完成
    优惠券写到一半        创建 hotfix 分支       改一行代码          合回 master
                                                    发布!
    
        3:35 PM              之后的两天              两天后
    ──────┼──────────────────┼────────────────────┼──────→
           │                  │                    │
    git stash pop 回现场   继续开发优惠券       rebase 整理历史
    继续写优惠券           把 master 合并进来    提 PR 等待审查
1
2
3
4
5
6
7
8
9
10
11
12

如果没有分支,小李只有两个选择:要么丢掉优惠券的半成品去修 bug,要么硬着头皮把半成品提交上去。无论哪种都是灾难。有了分支,两条时间线互不干扰,各自干净。


# 十、本章回顾

我学会了什么 一句话总结
分支的本质 一个 40 字节的指针文件,指向某个 commit
创建/切换分支 git switch -c <name> 创建并切换
Fast-forward 合并 没有分叉时,master 指针直接快进
三方合并 分叉后各自有提交,生成 merge commit
冲突的本质 同一行被两个分支改了,Git 不知道用哪个
解决冲突 编辑文件 → 删除标记 → git add → git commit
rebase 的原理 把 commit 拆下来,搬到目标分支最新点,重新安装
rebase 黄金法则 绝不对已 push 的公共 commit 做 rebase
交互式 rebase rebase -i:squash / drop / reword / fixup
merge vs rebase 公共分支用 merge,私有分支用 rebase

🎯 核心心法:分支是 Git 最强大的武器。不要怕分支,不要怕冲突。分支多 = 隔离好 = 出问题影响面小 = 你敢放心试。 频繁开分支、及时合分支、定期清理分支,是高级工程师的基本操作。


🏃 下一章预告:现在你一个人能玩转多条时间线了。下一步,连接世界——远程仓库 & 多人协作。你会学到:push / pull / fetch 的区别、为什么 fetch + merge 比 pull 更安全、以及 Pull Request 到底是怎么回事。


# 📎 本章涉及的命令速查

# === 分支操作 ===
git branch                          # 查看所有本地分支
git branch -v                       # 带最新 commit 信息
git branch <name>                   # 创建分支
git switch -c <name>                # 创建 + 切换(推荐)
git switch <name>                   # 切换分支
git branch -d <name>                # 安全删除(已合并)
git branch -D <name>                # 强制删除
git branch --merged                 # 查看已合并的分支
git branch --no-merged              # 查看未合并的分支

# === 合并 ===
git merge <branch>                  # 标准合并
git merge --no-ff <branch>          # 推荐:保留分支痕迹
git merge --abort                   # 放弃正在进行的合并

# === 变基 ===
git rebase <target-branch>          # 把当前分支 rebase 到目标分支
git rebase --abort                  # 放弃正在进行的 rebase
git rebase -i HEAD~N                # 交互式整理最近 N 个 commit

# === rebase -i 操作 ===
# pick    → 保留这个 commit
# reword  → 改 commit message
# squash  → 合并到上一个 commit(保留 message)
# fixup   → 合并到上一个 commit(丢弃 message)
# drop    → 删除这个 commit
# edit    → 停下来手改 commit 内容

# === 底层探索 ===
cat .git/refs/heads/<branch-name>   # 看分支指向哪个 commit
cat .git/HEAD                       # 看当前在哪个分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#Git
上次更新: 2026/06/07, 10:26:12
单人工作流:在 Git 的时光机里自由穿梭
远程协作:把你的代码推到全世界

← 单人工作流:在 Git 的时光机里自由穿梭 远程协作:把你的代码推到全世界→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式