Chapter 03

分支管理

理解 Git 最强大的特性——分支的本质、创建合并与冲突解决

什么是分支

分支(Branch)是 Git 中最强大的概念之一。很多人把分支想象成代码的"副本",但这是误解。

Git 中的分支本质上只是一个指针——指向某个 commit 的可移动引用。它实际上是一个只有 41 字节(40字符哈希 + 换行符)的文本文件,存放在 .git/refs/heads/ 目录中。

这意味着:创建一个分支的成本是 O(1),不论仓库有多大历史、多少文件,创建分支都是瞬间完成的。

A ── B ── C ── D ◄── main
└── E ── F ◄── feature/login

上图中,mainfeature/login 都只是指向不同 commit 的指针。C 和 D 在 main 上,E 和 F 只在 feature/login 上,但 A、B 两个 commit 被两条分支共享——没有数据重复,只是指针不同

HEAD 是什么

HEAD 是一个特殊的指针,始终指向你当前所在的分支(或者当前检出的某个 commit)。

# .git/HEAD 文件的内容通常是:
ref: refs/heads/main

# 当你 switch 到 feature 分支后,它变成:
ref: refs/heads/feature/login

当你运行 git log 看到 HEAD -> main 时,意思是:

Detached HEAD(游离 HEAD):当你直接 checkout 到某个 commit 的哈希时,HEAD 不再指向任何分支,而是直接指向那个 commit。此时如果创建新 commit,它不属于任何分支,很容易丢失。应立即创建一个分支:git switch -c new-branch

分支操作命令

查看分支

# 列出所有本地分支(当前分支前有 *)
git branch

# 列出所有分支(包括远程追踪分支)
git branch -a

# 列出远程分支
git branch -r

# 显示每个分支的最新提交
git branch -v

创建分支

# 仅创建分支,不切换
git branch feature-login

# 基于特定 commit 创建分支
git branch hotfix-v1.2 abc1234

切换分支

# 旧语法(仍然有效)
git checkout feature-login

# 新语法(Git 2.23+,推荐)— 语义更清晰
git switch feature-login

# 创建并切换(新语法,推荐)
git switch -c feature-payment

# 等同于上面的旧语法
git checkout -b feature-payment

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

为什么推荐 git switch?git checkout 功能太多(切换分支、恢复文件、检出特定 commit),语义模糊。Git 2.23 引入的 git switch(切换分支)和 git restore(恢复文件)语义更清晰。

删除分支

# 删除已合并的分支(安全删除,如果未合并会报错)
git branch -d feature-login

# 强制删除(不管是否已合并,危险!)
git branch -D feature-experiment

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

合并分支

合并(Merge)是将两条分支的历史整合在一起。在 main 上执行 git merge feature-login 意味着:把 feature-login 的变更整合进 main

# 先切换到目标分支(接收变更的分支)
git switch main

# 将 feature-login 合并进来
git merge feature-login

# 合并时强制创建 merge commit(--no-ff)
git merge --no-ff feature-login -m "Merge feature/login into main"

Fast-forward 合并

当目标分支(main)是源分支(feature)的直接祖先时,Git 默认进行 fast-forward 合并。此时不创建新的 merge commit,只是简单地移动 main 指针。

合并前:
main ──▶ A ── B ── C
└── D ── E ◄── feature

合并后(fast-forward,main 指针移动到 E):
A ── B ── C ── D ── E ◄── main ◄── feature

优点:历史保持线性,干净整洁。缺点:看不出哪些 commit 是在哪个 feature 上做的。

三方合并(Three-way Merge)

当两条分支都有各自的新 commit 时,Git 需要找到它们的共同祖先(merge base),进行三方比较,然后创建一个新的 merge commit

合并前:
main: A ── B ── C ── D ◄── main
└── E ── F ◄── feature

合并后(创建 merge commit M):
A ── B ── C ── D ────────── M ◄── main
└── E ── F ──────┘

merge commit M 有两个父 commit(D 和 F),完整保留了两条分支的开发历史。

冲突(Conflict)

当两条分支都修改了同一个文件的同一部分时,Git 无法自动决定用哪个版本,就会产生冲突

冲突标记

Git 会在冲突文件中插入标记:

<<<<<<< HEAD
function greet(name) {
  return `Hello, ${name}!`;
}
=======
function greet(user) {
  return `Hi there, ${user}! Welcome back.`;
}
>>>>>>> feature/greeting-update

解决冲突的步骤

# 1. 查看哪些文件有冲突
git status

# 2. 打开冲突文件,手动编辑,删除冲突标记,保留正确内容
#    (选择其中一个,或者合并两者)

# 3. 标记冲突已解决(重新 add)
git add src/utils.js

# 4. 完成合并
git commit # 会自动生成 merge commit 信息

# 或者,如果不想继续合并,可以放弃
git merge --abort

使用图形化工具解决冲突:VS Code 内置了优秀的冲突解决 UI,点击 "Accept Current Change" / "Accept Incoming Change" / "Accept Both Changes" 即可。也可配置 git config --global merge.tool vscode,然后 git mergetool 打开。

分支命名规范

良好的分支命名让团队一目了然地知道每个分支的用途:

分支类型命名格式示例说明
主分支main / mastermain生产就绪代码,永久存在
开发主线developdevelopGit Flow 中的开发集成分支
功能分支feature/xxxfeature/user-auth新功能开发,完成后合并删除
Bug 修复bugfix/xxxbugfix/login-error修复非紧急 bug
热修复hotfix/xxxhotfix/security-patch紧急生产 bug 修复
发布分支release/x.x.xrelease/2.1.0发布准备,只做小修改

查看分支图

这个命令组合是日常工作的必备,强烈建议为其设置别名:

# 图形化、单行、显示所有分支
git log --oneline --graph --all

# 设置别名(推荐!)
git config --global alias.lg "log --oneline --graph --all --decorate"

# 之后只需要输入
git lg