Git 完全指南 / 04 - 分支管理:branch、merge、rebase、cherry-pick
第四章:分支管理
分支是 Git 最强大的特性之一,让并行开发和实验变得轻而易举。
4.1 分支的本质
在 Git 中,分支仅仅是一个指向某个提交的可移动指针(lightweight pointer)。
main
↓
C1 → C2 → C3
↑
feature
默认分支通常叫 main(旧项目可能叫 master)。创建新分支时,Git 只创建一个 41 字节的文件,存储目标提交的 SHA-1 哈希值。
# 查看分支文件内容
$ cat .git/refs/heads/main
abc1234def5678901234567890abcdef12345678
4.2 分支基础操作
4.2.1 创建分支
# 创建新分支(不切换)
$ git branch feature-login
# 创建并切换到新分支
$ git checkout -b feature-login
# 或(Git 2.23+ 推荐)
$ git switch -c feature-login
# 基于特定提交创建分支
$ git branch hotfix-abc1234
# 基于远程分支创建本地分支
$ git branch feature origin/feature
$ git switch -c feature origin/feature
# 创建孤儿分支(无历史记录,用于 gh-pages 等)
$ git switch --orphan gh-pages
4.2.2 查看分支
# 查看本地分支
$ git branch
develop
* main
feature-login
# * 表示当前分支
# 查看所有分支(含远程)
$ git branch -a
* main
remotes/origin/main
remotes/origin/develop
# 查看远程分支
$ git branch -r
# 查看各分支最后提交
$ git branch -v
* main abc1234 Latest commit message
develop def5678 Dev branch commit
feature-login ghi9012 Add login form
# 查看已合并到当前分支的分支
$ git branch --merged
# 查看未合并的分支
$ git branch --no-merged
# 查看包含特定提交的分支
$ git branch --contains abc1234
4.2.3 切换分支
# 传统方式
$ git checkout feature-login
# 新方式(Git 2.23+,推荐)
$ git switch feature-login
# 切换到上一个分支
$ git switch -
$ git checkout -
# ⚠️ 切换前必须处理未提交的变更
# 方法 1:提交变更
$ git add -A && git commit -m "WIP"
# 方法 2:暂存变更
$ git stash
$ git switch feature
$ git stash pop
# 方法 3:强制切换(危险!可能丢失变更)
$ git switch -f feature
4.2.4 重命名分支
# 重命名当前分支
$ git branch -m new-name
# 重命名指定分支
$ git branch -m old-name new-name
# 强制重命名(覆盖已有分支名)
$ git branch -M new-name
4.2.5 删除分支
# 删除已合并的分支
$ git branch -d feature-login
# 强制删除(即使未合并)
$ git branch -D feature-login
# 删除远程分支
$ git push origin --delete feature-login
# 或
$ git push origin :feature-login
⚠️ 注意:不能删除当前所在的分支,需要先切换到其他分支。
4.3 合并(Merge)
4.3.1 快进合并(Fast-Forward)
当目标分支是当前分支的直接后继时,Git 只需移动指针:
合并前:
main: C1 → C2
↘
feature: C3 → C4
合并后(快进):
main: C1 → C2 → C3 → C4
$ git switch main
$ git merge feature
Updating abc1234..def5678
Fast-forward
src/login.js | 50 +++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
4.3.2 三方合并(Three-Way Merge)
当两个分支有分叉时,Git 创建一个新的合并提交:
合并前:
C4 (feature)
↗
C1 → C2 → C3 (main)
合并后:
C4 ──────┐
↗ ↘
C1 → C2 → C3 → C5 (merge commit)
$ git switch main
$ git merge feature
Merge made by the 'ort' strategy.
src/login.js | 50 +++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
# 合并时指定提交信息
$ git merge feature -m "Merge feature-login into main"
# 不自动提交(先审查合并结果)
$ git merge --no-commit feature
# 禁止快进合并(总是创建合并提交)
$ git merge --no-ff feature
# 仅允许快进合并(如果有分叉则失败)
$ git merge --ff-only feature
4.3.3 合并策略
| 策略 | 命令参数 | 适用场景 |
|---|---|---|
ort | 默认 | 通用场景,递归三方合并 |
ours | -s ours | 丢弃被合并分支的内容 |
theirs | -X theirs | 保留被合并分支的内容 |
subtree | -s subtree | 合并子目录项目 |
# 使用 ours 策略(只保留当前分支内容,但记录合并历史)
$ git merge -s ours feature
# 解决冲突时偏好被合并分支
$ git merge -X theirs feature
# 解决冲突时偏好当前分支
$ git merge -X ours feature
4.4 变基(Rebase)
变基将当前分支的提交"重放"到目标分支之上,产生线性历史。
变基前:
C4 → C5 (feature)
↗
C1 → C2 → C3 (main)
变基后(在 feature 上执行 git rebase main):
C4' → C5' (feature)
↗
C1 → C2 → C3 (main)
$ git switch feature
$ git rebase main
Successfully rebased and updated refs/heads/feature.
# 变基后切回 main 合并(快进合并)
$ git switch main
$ git merge feature
Updating abc1234..def5678
Fast-forward
Merge vs Rebase 对比
| 特性 | Merge | Rebase |
|---|---|---|
| 历史记录 | 保留分支结构 | 线性历史 |
| 合并提交 | 创建 merge commit | 无额外提交 |
| 安全性 | 不修改历史 | 重写历史 |
| 冲突处理 | 一次性解决 | 逐个提交解决 |
| 适用场景 | 公共分支 | 个人分支 |
| 可追溯性 | 高 | 中 |
⚠️ 黄金法则:不要对已经推送到远程的公共分支执行 rebase!
4.5 Cherry-Pick
Cherry-pick 将某个特定提交应用到当前分支:
# 拣选单个提交
$ git cherry-pick abc1234
# 拣选多个提交
$ git cherry-pick abc1234 def5678 ghi9012
# 拣选提交范围(不包含起始提交)
$ git cherry-pick abc1234..ghi9012
# 拣选提交范围(包含起始提交)
$ git cherry-pick abc1234^..ghi9012
# 只暂存不提交
$ git cherry-pick --no-commit abc1234
# 保留原始作者信息
$ git cherry-pick -x abc1234 # 添加 "cherry picked from" 注释
适用场景:
- 将 bugfix 从开发分支应用到发布分支
- 从一个功能分支移植特定提交到另一个分支
- 热修复(hotfix)应用
4.6 解决合并冲突
当两个分支修改了同一文件的同一区域时,会产生冲突。
冲突标记
$ git merge feature
Auto-merging src/config.js
CONFLICT (content): Merge conflict in src/config.js
Automatic merge failed; fix conflicts and then commit the result.
冲突文件内容:
<<<<<<< HEAD
const API_URL = "https://api.production.com";
=======
const API_URL = "https://api.staging.com";
>>>>>>> feature
| 标记 | 含义 |
|---|---|
<<<<<<< HEAD | 当前分支的内容开始 |
======= | 分隔符 |
>>>>>>> feature | 被合并分支的内容结束 |
解决冲突步骤
# 1. 查看冲突文件
$ git status
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/config.js
# 2. 编辑冲突文件(手动选择或合并内容)
$ vim src/config.js
# 修改为:
const API_URL = "https://api.production.com";
# 3. 标记为已解决
$ git add src/config.js
# 4. 完成合并
$ git commit -m "Merge feature, resolve config conflict"
使用合并工具
# 启动配置的合并工具
$ git mergetool
# 使用 VS Code
$ git mergetool --tool=vscode
使用 git checkout --ours/--theirs
# 保留当前分支版本
$ git checkout --ours src/config.js
$ git add src/config.js
# 使用被合并分支版本
$ git checkout --theirs src/config.js
$ git add src/config.js
4.7 分支管理最佳实践
分支命名规范
类型/简短描述
示例:
feature/user-authentication
feature/payment-integration
bugfix/login-timeout
hotfix/security-patch
release/v1.2.0
chore/update-dependencies
| 前缀 | 用途 | 生命周期 |
|---|---|---|
feature/ | 新功能 | 功能完成后合并 |
bugfix/ | Bug 修复 | 修复后合并 |
hotfix/ | 生产环境紧急修复 | 立即合并 |
release/ | 发布准备 | 发布后合并 |
chore/ | 杂项 | 完成后合并 |
experiment/ | 实验性代码 | 验证后删除或合并 |
分支保护规则(GitHub/GitLab)
| 规则 | 说明 |
|---|---|
| 禁止直接推送 | 必须通过 PR/MR |
| 要求代码审查 | 至少 N 人批准 |
| 要求 CI 通过 | 所有检查必须绿灯 |
| 禁止 force push | 防止历史被重写 |
| 要求线性历史 | 禁止 merge commit |
业务场景
| 场景 | 推荐方案 |
|---|---|
| 日常功能开发 | git switch -c feature/xxx 开发完成后合并 |
| 紧急线上修复 | git switch -c hotfix/xxx 从 main 拉出,修复后合并 |
| 版本发布 | git switch -c release/v1.0 测试后合并到 main 并打标签 |
| 接收他人修复 | git cherry-pick <commit> |
| 整理个人分支历史 | git rebase -i main 压缩提交 |
| 长期并行开发 | 主开发分支 + 功能分支,定期 rebase |
扩展阅读
- git-scm.com: Git Branching
- A successful Git branching model — Git Flow
- GitHub Flow
- Trunk-Based Development