Skip to content

第二阶段:Git 分支管理


一、分支基础

1.1 什么是分支?

分支(Branch)是 Git 最强大的特性之一。本质上,分支就是一个指向某个提交对象的可移动指针。

         main

C0 ← C1 ← C2

创建新分支时,Git 只是创建了一个新的指针,不会复制任何文件,所以创建分支极其轻量和快速。

         main

C0 ← C1 ← C2

        feature

HEAD 指针: Git 用一个特殊指针 HEAD 来标记"你当前在哪个分支上"。

       HEAD

       main

C0 ← C1 ← C2

      feature

1.2 git branch — 创建、查看、删除分支

bash
# 查看本地分支(* 标记当前分支)
git branch

# 查看所有分支(含远程)
git branch -a

# 查看远程分支
git branch -r

# 查看每个分支的最后一次提交
git branch -v

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

# 查看未合并的分支
git branch --no-merged

# 创建新分支(不切换)
git branch feature-login

# 重命名分支
git branch -m old-name new-name

# 重命名当前分支
git branch -m new-name

# 删除已合并的分支
git branch -d feature-login

# 强制删除未合并的分支
git branch -D feature-login

# 删除远程分支
git push origin --delete feature-login

1.3 git checkout / git switch — 切换分支

git switch 是 Git 2.23 引入的新命令,专门用于分支切换,语义更清晰。

bash
# ——— 使用 git switch(推荐) ———

# 切换到已有分支
git switch feature-login

# 创建并切换到新分支
git switch -c feature-signup

# 基于某个提交创建并切换
git switch -c hotfix abc1234

# 切换回上一个分支
git switch -

# ——— 使用 git checkout(旧方式) ———

# 切换到已有分支
git checkout feature-login

# 创建并切换到新分支
git checkout -b feature-signup

# 基于远程分支创建本地跟踪分支
git checkout -b feature origin/feature

# 切换回上一个分支
git checkout -

git switch vs git checkout 的区别:

功能git switchgit checkout
切换分支
创建并切换switch -ccheckout -b
恢复文件❌(用 git restore✅(checkout -- file
语义清晰度高(只做分支切换)低(身兼数职)

推荐: 使用 git switch 切换分支,git restore 恢复文件,职责分离更不易出错。

切换分支前的注意事项:

切换分支时,如果工作区或暂存区有未提交的修改,Git 的行为取决于这些修改是否与目标分支冲突:

  • 不冲突 — 修改会被带到目标分支(这可能不是你想要的)
  • 冲突 — Git 拒绝切换,提示你先处理
bash
# 安全做法:切换前先暂存工作
git stash           # 暂存当前修改
git switch other    # 切换分支
# ... 做完事情后 ...
git switch -        # 切回来
git stash pop       # 恢复暂存的修改

1.4 git merge — 合并分支

将另一个分支的修改合并到当前分支。

bash
# 先切换到目标分支(通常是 main)
git switch main

# 合并 feature 分支
git merge feature-login

# 合并时创建合并提交(即使可以快速合并)
git merge --no-ff feature-login

# 合并但不自动提交(方便检查合并结果)
git merge --no-commit feature-login

# 取消正在进行的合并
git merge --abort

1.5 快速合并(Fast-forward)与三方合并(3-way Merge)

Fast-forward 合并

当目标分支没有新提交时,Git 直接将指针前移,不产生合并提交。

合并前:
         main

C0 ← C1 ← C2 ← C3 ← C4

                     feature

合并后(git merge feature):
                       main

C0 ← C1 ← C2 ← C3 ← C4

                     feature

特点:历史线性,干净清爽,但看不出"曾有分支"的痕迹。

三方合并(3-way Merge)

当两个分支各自都有新提交时,Git 会找到共同祖先,创建一个新的合并提交。

合并前:
              main

C0 ← C1 ← C2 ← C5

           C3 ← C4

              feature

合并后(git merge feature):
                    main

C0 ← C1 ← C2 ← C5 ← M(合并提交)
          ↖         ↗
           C3 ← C4

              feature

特点:保留了完整的分支历史,能看出功能是在哪个分支上开发的。

--no-ff 的意义

bash
git merge --no-ff feature-login

即使可以快速合并,也强制创建一个合并提交。这样做的好处是:

  • 保留"这是一个完整功能"的信息
  • 方便用 git revert 整体回退该功能
  • git log --graph 中能清晰看到分支结构
# 不用 --no-ff(Fast-forward):
C0 ← C1 ← C2 ← C3 ← C4    (看不出 C3、C4 是在分支上开发的)

# 使用 --no-ff:
C0 ← C1 ← C2 ────────── M  (能看出 C3、C4 来自分支)
          ↖            ↗
           C3 ← C4

1.6 合并冲突的产生与解决

什么时候会产生冲突?

当两个分支修改了同一个文件的同一区域时,Git 无法自动判断应该保留哪个版本。

冲突长什么样?

<<<<<<< HEAD
这是当前分支(main)的内容
=======
这是被合并分支(feature)的内容
>>>>>>> feature

三个标记的含义:

标记含义
<<<<<<< HEAD冲突开始,下面是当前分支的内容
=======分隔线
>>>>>>> feature冲突结束,上面是被合并分支的内容

解决冲突的步骤

bash
# ① 执行合并,Git 提示冲突
git merge feature
# Auto-merging file.txt
# CONFLICT (content): Merge conflict in file.txt
# Automatic merge failed; fix conflicts and then commit the result.

# ② 查看哪些文件有冲突
git status
# both modified: file.txt

# ③ 打开冲突文件,手动编辑
# 删除冲突标记,保留你想要的内容(可以保留一方、合并双方、或写全新内容)

# ④ 标记冲突已解决
git add file.txt

# ⑤ 完成合并提交
git commit -m "merge: 合并 feature 分支,解决冲突"

使用工具解决冲突

bash
# 使用图形化合并工具
git mergetool

# 配置默认合并工具
git config --global merge.tool vimdiff

实际开发中: 大多数 IDE(VS Code、IntelliJ 等)都有内置的冲突解决界面,可以直观地选择保留哪些内容,比命令行方便得多。


二、分支策略

2.1 Git Flow 工作流

最经典的分支模型,适合有固定发布周期的项目。

时间轴 →

main        ──●──────────────────●──────────────●── (只有发布版本)
              │                  ↑              ↑
              │                  │              │
release       │          ●──●──●─┘      ●──●──●─┘   (发布准备)
              │          ↑                ↑
              │          │                │
develop    ───●──●──●──●─┴──●──●──●──●──●─┴──●──●── (开发主线)
                 ↑     ↑        ↑     ↑
                 │     │        │     │
feature          ●──●──┘        ●──●──┘              (功能分支)

hotfix        ──────●──●──→ main & develop           (紧急修复)

五种分支角色:

分支生命周期来源合并到用途
main永久线上稳定版本,每次合并打 Tag
develop永久mainrelease开发主线,集成所有功能
feature/*临时developdevelop开发新功能
release/*临时developmain + develop发布前的测试和修复
hotfix/*临时mainmain + develop线上紧急 Bug 修复

适用场景: 中大型项目、有明确版本发布计划、多团队协作。

缺点: 分支多、流程重,对于持续部署的项目过于复杂。

2.2 GitHub Flow 工作流

极简的分支模型,只有一个长期分支 main,配合 Pull Request。

main     ──●──●──────●──────────●──●──
              ↖      ↑          ↑
               ●──●──┘   ●──●──┘
             feature-A   feature-B

流程:

  1. main 创建功能分支
  2. 在功能分支上开发并提交
  3. 发起 Pull Request(PR)
  4. 团队 Code Review
  5. 通过 CI 测试
  6. 合并到 main,自动部署
  7. 删除功能分支

适用场景: 持续部署的 Web 项目、小团队、开源项目。

优点: 简单直观,配合 PR 流程质量可控。

2.3 Trunk-Based Development(主干开发)

所有开发者直接在 main(主干)上工作,或使用极短生命周期的分支。

main  ──●──●──●──●──●──●──●──●──●──
           ↖  ↑     ↖  ↑
            ●─┘      ●─┘
         (极短分支,通常 < 1 天)

核心原则:

  • 分支存活时间不超过 1-2 天
  • 频繁地向主干合并(至少每天一次)
  • 依赖 Feature Flag(功能开关)控制未完成功能的可见性
  • 强依赖 CI/CD 和自动化测试

适用场景: 高频发布、经验丰富的团队、完善的自动化测试体系。

2.4 功能分支(Feature Branch)最佳实践

无论使用哪种工作流,功能分支的管理都遵循一些通用原则:

命名规范:

bash
# 推荐格式:类型/简短描述
feature/user-login
feature/shopping-cart
bugfix/fix-header-overflow
hotfix/security-patch
refactor/database-layer
docs/api-documentation

# 也可以加上 Issue 编号
feature/PROJ-123-user-login
bugfix/PROJ-456-fix-crash

生命周期管理:

bash
# ① 创建功能分支
git switch -c feature/user-login main

# ② 开发过程中定期同步主干(避免最终合并时冲突过多)
git fetch origin
git merge origin/main
# 或使用 rebase(保持线性历史)
git rebase origin/main

# ③ 完成后合并回主干
git switch main
git merge --no-ff feature/user-login

# ④ 删除已合并的分支
git branch -d feature/user-login
git push origin --delete feature/user-login

最佳实践总结:

原则说明
小而专注每个分支只做一件事,避免"超级大分支"
短生命周期分支存活越久,合并冲突越多
定期同步经常从主干拉取最新代码
及时清理合并后立即删除功能分支
清晰命名一看就知道这个分支在做什么

2.5 三种工作流的选择指南

你的项目是什么类型?

├─ 有固定版本发布周期(如每月/每季度发版)
│   └─→ Git Flow

├─ 持续部署的 Web 应用
│   ├─ 团队较小(< 10 人)
│   │   └─→ GitHub Flow
│   └─ 团队较大、测试完善
│       └─→ Trunk-Based Development

└─ 开源项目
    └─→ GitHub Flow(Fork + PR)

三、实践 Demo

Demo 1:分支的创建、切换与合并

bash
# ① 初始化项目
mkdir branch-demo && cd branch-demo
git init
echo "# Branch Demo" > README.md
git add . && git commit -m "feat: 初始化项目"

# ② 创建并切换到功能分支
git switch -c feature/add-hello

# ③ 在功能分支上开发
echo "Hello from feature branch!" > hello.txt
git add . && git commit -m "feat: 添加 hello.txt"

echo "Another line" >> hello.txt
git add . && git commit -m "feat: 更新 hello.txt"

# ④ 查看分支图
git log --oneline --graph --all

# ⑤ 切回 main 合并
git switch main
git merge feature/add-hello

# ⑥ 查看合并结果
git log --oneline --graph --all
cat hello.txt

# ⑦ 删除已合并的分支
git branch -d feature/add-hello

Demo 2:体验 Fast-forward 与 --no-ff 的区别

bash
# ① 准备一个干净的仓库
mkdir merge-demo && cd merge-demo
git init
echo "base" > file.txt
git add . && git commit -m "feat: 初始提交"

# ——— 场景 A:Fast-forward 合并 ———

git switch -c feature-a
echo "feature a" >> file.txt
git add . && git commit -m "feat: feature-a 修改"

git switch main
git merge feature-a          # Fast-forward,没有合并提交
git log --oneline --graph --all
git branch -d feature-a

# ——— 场景 B:--no-ff 合并 ———

git switch -c feature-b
echo "feature b" >> file.txt
git add . && git commit -m "feat: feature-b 修改"

git switch main
git merge --no-ff feature-b  # 强制创建合并提交
git log --oneline --graph --all
git branch -d feature-b

# 对比两种方式在 log --graph 中的区别

Demo 3:模拟并解决合并冲突

bash
# ① 初始化
mkdir conflict-demo && cd conflict-demo
git init
echo "line 1: original" > story.txt
echo "line 2: original" >> story.txt
echo "line 3: original" >> story.txt
git add . && git commit -m "feat: 初始故事"

# ② 在 main 上修改第 2 行
sed -i 's/line 2: original/line 2: modified by main/' story.txt
git add . && git commit -m "feat: main 修改了第 2 行"

# ③ 创建分支(基于上一次共同祖先)
git switch -c feature HEAD~1

# ④ 在 feature 上也修改第 2 行
sed -i 's/line 2: original/line 2: modified by feature/' story.txt
git add . && git commit -m "feat: feature 修改了第 2 行"

# ⑤ 切回 main 合并 — 此时会产生冲突!
git switch main
git merge feature
# 输出:CONFLICT (content): Merge conflict in story.txt

# ⑥ 查看冲突状态
git status

# ⑦ 查看冲突内容
cat story.txt
# 你会看到 <<<<<<< ======= >>>>>>> 标记

# ⑧ 手动解决冲突(编辑文件,选择你想要的内容)
# 例如保留两个分支的修改:
cat > story.txt << 'EOF'
line 1: original
line 2: modified by main and feature
line 3: original
EOF

# ⑨ 标记已解决并提交
git add story.txt
git commit -m "merge: 合并 feature,解决第 2 行冲突"

# ⑩ 查看合并结果
git log --oneline --graph --all

Demo 4:模拟 GitHub Flow 工作流

bash
# ① 初始化项目
mkdir github-flow-demo && cd github-flow-demo
git init
echo "# My App" > README.md
echo "v1.0" > version.txt
git add . && git commit -m "feat: 初始化项目 v1.0"

# ② 开发者 A:开发登录功能
git switch -c feature/user-login
echo "function login() { }" > auth.js
git add . && git commit -m "feat: 添加登录功能框架"
echo "function validateUser() { }" >> auth.js
git add . && git commit -m "feat: 添加用户验证"

# ③ 同时,main 上有了新的提交(模拟其他人的合并)
git switch main
echo "v1.1" > version.txt
git add . && git commit -m "chore: 更新版本号"

# ④ 开发者 A 先同步主干的最新代码
git switch feature/user-login
git merge main  # 或 git rebase main

# ⑤ 合并功能分支到 main(模拟 PR 合并)
git switch main
git merge --no-ff feature/user-login -m "merge: 合并登录功能 (#1)"

# ⑥ 清理
git branch -d feature/user-login

# ⑦ 查看完整历史
git log --oneline --graph --all

Demo 5:模拟 Git Flow 的 hotfix 流程

bash
# 基于上一个 demo 继续

# ① 发现线上 Bug,紧急修复
git switch -c hotfix/fix-auth-bug main

# ② 修复 Bug
echo "function login() { /* fixed */ }" > auth.js
git add . && git commit -m "fix: 修复登录验证漏洞"

# ③ 合并到 main(发布修复)
git switch main
git merge --no-ff hotfix/fix-auth-bug -m "merge: 紧急修复登录漏洞"

# ④ 打标签标记版本
git tag -a v1.1.1 -m "hotfix: 修复登录验证漏洞"

# ⑤ 清理 hotfix 分支
git branch -d hotfix/fix-auth-bug

# ⑥ 查看标签和历史
git tag
git log --oneline --graph --all --decorate

四、常见问题 Q&A

Q1:切换分支时提示有未提交的修改,怎么办?

A: 有三种处理方式:

bash
# 方式一:暂存(推荐,最常用)
git stash                   # 暂存修改
git switch other-branch     # 切换
# ...做完事情后...
git switch -                # 切回来
git stash pop               # 恢复修改

# 方式二:提交(修改已经告一段落)
git add . && git commit -m "wip: 保存进度"
git switch other-branch

# 方式三:丢弃(不要这些修改了)
git restore .               # 丢弃所有工作区修改
git switch other-branch

Q2:git mergegit rebase 有什么区别?该用哪个?

A:

特性git mergegit rebase
操作方式创建一个合并提交将提交"移植"到目标分支末端
历史记录保留真实的分支历史生成线性历史
安全性不改写历史,安全改写历史,对已推送的分支危险
冲突处理一次性解决所有冲突逐个提交解决冲突
# merge 的结果:
      main

A ← B ← E ← M (合并提交)
    ↖       ↗
     C ← D

     feature

# rebase 的结果:
             main

A ← B ← E ← C' ← D'  (线性历史,C 和 D 被"重放"为 C' 和 D')

选择建议:

  • 个人本地分支 → 用 rebase,保持历史干净
  • 公共分支 / 已推送 → 用 merge,不要 rebase
  • 团队约定 → 统一标准,避免混乱

黄金法则: 永远不要 rebase 已经推送到远程的公共分支。


Q3:怎么撤销一次已完成的合并?

A:

bash
# 如果合并还没推送到远程
git reset --hard HEAD~1  # 回到合并前的状态

# 如果合并已推送到远程(安全做法)
git revert -m 1 <merge-commit-hash>
# -m 1 表示保留主分支的版本

Q4:误删了分支怎么恢复?

A: 只要提交还在(没有被 GC 清理),就可以恢复:

bash
# 查看所有操作记录(包括被删除的分支指向的提交)
git reflog

# 找到被删分支最后一次提交的 hash,重新创建分支
git branch recovered-branch abc1234

Q5:两个分支修改了不同的文件,会产生冲突吗?

A: 不会。Git 会自动合并。只有同一文件的同一区域被两个分支同时修改才会产生冲突。

常见不会冲突的场景:

  • 两个分支修改了不同的文件
  • 两个分支修改了同一文件的不同区域
  • 一个分支新增文件,另一个分支没动

常见会冲突的场景:

  • 同一文件的同一行被不同分支修改
  • 一个分支删除了文件,另一个分支修改了该文件
  • 两个分支在同一位置添加了不同内容

Q6:如何查看某个分支和 main 的区别?

A:

bash
# 查看 feature 分支相比 main 新增了哪些提交
git log main..feature --oneline

# 查看文件差异
git diff main..feature

# 只看哪些文件变化了
git diff main..feature --name-only

# 查看分支分叉以来的所有提交
git log main...feature --oneline

.. vs ... 的区别:

  • main..feature:feature 有但 main 没有的提交(单向)
  • main...feature:main 和 feature 各自独有的提交(双向)

Q7:合并时显示 "Already up to date" 是什么意思?

A: 说明当前分支已经包含了目标分支的所有提交,没有需要合并的内容。常见原因:

  • 该分支之前已经被合并过了
  • 当前分支本身就是从目标分支创建的,且目标分支没有新提交

Q8:git branch -dgit branch -D 的区别?

A:

命令行为
git branch -d安全删除,只能删除已合并到当前分支的分支
git branch -D强制删除,无论是否合并
bash
# 安全删除(如果未合并会报错提醒)
git branch -d feature-x
# error: The branch 'feature-x' is not fully merged.

# 强制删除(确认不需要了再用)
git branch -D feature-x

Q9:多人协作时分支冲突频繁怎么办?

A: 减少冲突的策略:

  1. 缩短分支生命周期 — 功能尽量拆小,快速合并
  2. 频繁同步主干 — 每天 git pull origin main 一次
  3. 合理划分模块 — 避免多人同时修改同一文件
  4. 先沟通再开发 — 涉及公共文件时提前告知
  5. 使用 --no-ff — 保持合并记录,方便排查问题

Q10:本地分支太多太乱怎么清理?

A:

bash
# 查看已合并的分支(可安全删除)
git branch --merged main

# 批量删除已合并的分支(排除 main 和当前分支)
git branch --merged main | grep -v "main\|^\*" | xargs git branch -d

# 清理远程已删除但本地还存在的远程跟踪分支
git fetch --prune

# 或一步到位
git remote prune origin

# 查看远程已删除的分支
git remote prune origin --dry-run

五、分支操作速查表

┌──────────────────────────────────────────────────────────────┐
│                     Git 分支命令速查                          │
├────────────────────────────┬─────────────────────────────────┤
│ git branch                 │ 查看本地分支                     │
│ git branch -a              │ 查看所有分支(含远程)            │
│ git branch <name>          │ 创建分支                         │
│ git branch -d <name>       │ 删除已合并的分支                  │
│ git branch -D <name>       │ 强制删除分支                     │
│ git branch -m <new>        │ 重命名当前分支                   │
│ git branch --merged        │ 查看已合并的分支                  │
│ git switch <name>          │ 切换分支                         │
│ git switch -c <name>       │ 创建并切换分支                   │
│ git switch -               │ 切回上一个分支                   │
│ git merge <branch>         │ 合并分支                         │
│ git merge --no-ff <branch> │ 合并并强制创建合并提交            │
│ git merge --abort          │ 取消进行中的合并                  │
│ git stash                  │ 暂存当前修改                     │
│ git stash pop              │ 恢复暂存的修改                   │
│ git log --graph --all      │ 查看分支图                       │
│ git diff main..feature     │ 比较两个分支的差异               │
│ git fetch --prune          │ 清理远程已删除的分支              │
└────────────────────────────┴─────────────────────────────────┘

六、学习检查清单

完成以下任务,确认你已掌握本阶段内容:

  • [ ] 能说出 Git 分支的本质(指针)
  • [ ] 能使用 git switch -c 创建并切换到新分支
  • [ ] 能区分 Fast-forward 合并和三方合并
  • [ ] 能解释 --no-ff 参数的作用和使用场景
  • [ ] 能手动解决一个合并冲突
  • [ ] 能画出 Git Flow 的分支模型图
  • [ ] 能说出 Git Flow、GitHub Flow、Trunk-Based 各自适用的场景
  • [ ] 能按照规范命名功能分支
  • [ ] 能使用 git stash 在切换分支前保存工作进度
  • [ ] 能使用 git log --graph 查看分支合并历史