跳到主要内容

分支管理

本章将深入介绍 Git 的分支操作,包括创建、切换、合并分支、解决冲突,以及在实际开发中的分支策略。

什么是分支?

分支是 Git 最强大的功能之一。在 Git 中,分支本质上只是一个指向某个提交的可变指针。理解这一点很重要——分支不是一个目录的副本,而仅仅是一个 41 字节的文件,存储着一个提交的 SHA-1 值。

这意味着创建和切换分支几乎是瞬间完成的,因为 Git 只需要写入 41 个字节。

每个分支都是独立的开发线,可以:

  • 并行开发:多人同时在不同功能上工作,互不干扰
  • 隔离实验:尝试新想法,失败了随时可以丢弃
  • 安全修复:修复 bug 时不影响正在进行的功能开发
  • 代码审查:通过 Pull Request 进行代码审查后再合并

HEAD 指针

在讨论分支之前,需要理解 HEAD 这个概念。HEAD 是一个特殊的指针,指向当前所在的分支(或者在分离 HEAD 状态下直接指向某个提交)。

# 查看 HEAD 指向
cat .git/HEAD
# 输出:ref: refs/heads/main

# 切换分支后,HEAD 会更新指向
cat .git/HEAD
# 输出:ref: refs/heads/feature-login

HEAD 告诉 Git:"我现在在这个位置"。当你提交新内容时,新的提交会加在 HEAD 所指分支的后面。

查看分支

# 查看本地分支(* 表示当前分支)
git branch

# 查看所有分支(本地+远程)
git branch -a

# 查看远程分支
git branch -r

# 查看分支最后一次提交信息
git branch -v

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

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

创建分支

基本创建

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

# 创建并切换到新分支(旧方式)
git checkout -b feature-login

# Git 2.23+ 的新命令
git switch -c feature-login

创建分支的过程:

从上图可以看到,分支从 main 的最后一个提交处创建,此时两个分支指向同一个提交。之后在 feature-login 上的提交不会影响 main 分支。

基于特定提交创建分支

# 基于某个提交创建分支
git branch hotfix abc1234

# 基于标签创建分支
git branch release/v1.0 v1.0.0

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

# 创建孤儿分支(没有历史的分支,常用于 gh-pages)
git checkout --orphan gh-pages

基于其他分支创建

# 基于某个分支的特定位置创建
git branch new-feature main~3

# 从远程分支检出并跟踪
git checkout -b local-branch origin/remote-branch

# 或使用 switch
git switch -c local-branch origin/remote-branch

切换分支

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

# 切换到上一个分支
git checkout -
git switch -

# 强制切换(丢弃本地修改)
git checkout -f main

切换分支时的注意事项

切换分支前,Git 会检查工作区状态。如果有未提交的修改,可能会导致切换失败:

# 如果有未提交的修改,Git 会阻止切换
error: Your local changes to the following files would be overwritten by checkout:
app.js
Please commit your changes or stash them before you switch branches.

解决方案:

# 方案1:提交修改
git add .
git commit -m "保存当前工作"

# 方案2:储藏修改(推荐)
git stash
git checkout other-branch
# 工作完成后切回来
git checkout original-branch
git stash pop

重命名分支

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

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

删除分支

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

# 强制删除本地分支(即使未合并)
git branch -D feature-login

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

# 也可以用推送空分支的方式
git push origin :feature-login

删除分支时,只是删除了指向提交的指针,提交本身仍然存在于 Git 的对象数据库中。

合并分支

快进合并(Fast-forward)

当目标分支没有新提交时,Git 可以直接将指针向前移动:

# 确保在目标分支上
git checkout main

# 合并功能分支
git merge feature-login

快进合并前:

快进合并后(指针直接移动):

这种情况下,Git 只是简单地移动 main 指针,不会创建新的提交。

三方合并(Three-way Merge)

当目标分支也有新提交时,Git 需要做三方合并:

三方合并前:

三方合并后(创建合并提交):

Git 会找到两个分支的共同祖先(B),然后将两个分支的修改合并,创建一个新的合并提交。

合并策略选项

# 强制创建合并提交(即使是快进合并)
git merge --no-ff feature-login

# 仅快进合并,如果不能快进就失败
git merge --ff-only feature-login

# 压缩合并(将所有提交压缩为一个)
git merge --squash feature-login
git commit -m "合并功能分支"

--no-ff 的意义:即使可以快进合并,也创建一个合并提交。这样做的好处是保留分支的历史信息,在 git log 中可以清楚地看到哪些提交属于哪个功能分支。

解决合并冲突

冲突的产生

当两个分支修改了同一个文件的同一部分时,会出现冲突:

$ git merge feature-login
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.

查看冲突

冲突文件的内容会变成这样:

function greet(name) {
<<<<<<< HEAD
return `你好,${name}!欢迎回来`;
=======
return `Hello, ${name}! Welcome back`;
>>>>>>> feature-login
}
  • <<<<<<< HEAD======= 之间是当前分支的内容
  • =======>>>>>>> feature-login 之间是要合并分支的内容

解决冲突

手动编辑文件,选择保留的内容,删除冲突标记:

function greet(name) {
return `你好,${name}!欢迎回来`;
}

然后标记冲突已解决并提交:

git add app.js
git commit

使用合并工具

# 启动配置的合并工具
git mergetool

# 查看可用的合并工具
git mergetool --tool-help

取消合并

如果在解决冲突过程中发现合并错误,可以取消:

git merge --abort

这会恢复到合并前的状态。

变基(Rebase)

变基是另一种整合分支修改的方式,它将一系列提交"移动"到另一个基点上。

基本变基

# 将 feature 分支变基到 main
git checkout feature
git rebase main

变基前:

变基后:

注意:变基后 C' 和 D' 是新的提交,哈希值与原来不同。

变基的工作原理

变基不是移动提交,而是:

  1. 找到两个分支的共同祖先
  2. 获取当前分支相对于共同祖先的提交
  3. 在目标分支上重新应用这些提交
  4. 更新当前分支指针

交互式变基

# 修改最近 3 次提交
git rebase -i HEAD~3

打开编辑器后,可以对提交进行操作:

pick abc1234 第一个提交
pick def5678 第二个提交
pick ghi9012 第三个提交

# 可用命令:
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交信息
# e, edit = 使用提交,但停下来修改
# s, squash = 使用提交,但合并到前一个提交
# f, fixup = 类似 squash,但丢弃提交信息
# d, drop = 删除提交

变基的使用场景

变基适合:

  • 保持提交历史整洁线性
  • 在合并前更新功能分支的代码
  • 整理本地提交

合并适合:

  • 保留完整的分支历史
  • 需要清晰的合并记录
  • 多人协作的公共分支

黄金法则

不要对已经推送到远程的提交进行变基。因为变基会改变提交的哈希值,如果其他人已经基于这些提交工作,会导致混乱。

比较分支

# 比较两个分支的差异
git diff main..feature-login

# 比较两个分支的提交(只在 feature-login 中的提交)
git log main..feature-login

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

Cherry-pick

将某个特定提交应用到当前分支:

# 应用单个提交
git cherry-pick abc1234

# 应用多个提交
git cherry-pick abc1234 def5678

# 应用但不自动提交
git cherry-pick --no-commit abc1234

这在以下场景很有用:

  • 修复了 bug 后,需要将修复应用到多个分支
  • 从其他分支提取特定的提交

分支命名规范

# 功能分支
feature/user-login
feature/add-payment
feat/short-name

# 修复分支
bugfix/fix-login-error
fix/header-alignment

# 发布分支
release/1.0.0
release/v2.0

# 热修复分支
hotfix/security-patch
hotfix/critical-bug

# 文档分支
docs/update-readme
docs/api-documentation

# 测试分支
test/add-unit-tests
test/e2e-tests

# 重构分支
refactor/clean-up-auth
refactor/optimize-queries

常见分支工作流

GitHub Flow

适合持续部署的简单工作流:

# 1. 从 main 创建功能分支
git checkout -b feature/new-feature

# 2. 提交修改
git add .
git commit -m "添加新功能"

# 3. 推送到远程
git push origin feature/new-feature

# 4. 创建 Pull Request

# 5. 代码审查后合并到 main

# 6. 删除功能分支
git branch -d feature/new-feature

Git Flow

适合发布周期明确的项目:

# 主要分支:
# - main:生产代码
# - develop:开发代码

# 支持分支:
# - feature/*:功能开发
# - release/*:发布准备
# - hotfix/*:紧急修复

# 创建功能分支
git checkout develop
git checkout -b feature/new-feature

# 功能完成后合并回 develop
git checkout develop
git merge --no-ff feature/new-feature

# 创建发布分支
git checkout develop
git checkout -b release/1.0.0

# 发布完成后合并到 main 和 develop
git checkout main
git merge --no-ff release/1.0.0
git checkout develop
git merge --no-ff release/1.0.0

实用技巧

搜索分支中的内容

# 在所有分支中搜索包含特定字符串的提交
git log --all --grep="bug fix"

# 搜索包含特定代码的提交
git log -S "functionName" --all

设置跟踪分支

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

# 设置已有分支跟踪远程分支
git branch -u origin/feature

# 查看跟踪关系
git branch -vv

分支清理

# 删除已合并到当前分支的分支
git branch --merged | grep -v "\*" | xargs git branch -d

# 清理已删除的远程分支的本地引用
git fetch --prune

小结

本章我们学习了:

  1. 分支的本质:指向提交的可变指针,创建和切换几乎瞬间完成
  2. HEAD 指针:表示当前所在位置
  3. 创建和切换git branchgit checkoutgit switch
  4. 合并分支:快进合并和三方合并的区别
  5. 解决冲突:理解冲突标记,手动解决或使用合并工具
  6. 变基:线性化历史,但要遵守黄金法则
  7. Cherry-pick:选择性应用提交
  8. 分支策略:GitHub Flow、Git Flow 等工作流

练习

  1. 创建两个功能分支并切换
  2. 在不同分支上修改同一个文件并提交
  3. 尝试合并分支并解决冲突
  4. 使用变基整理提交历史
  5. 使用 cherry-pick 将一个提交应用到另一个分支
  6. 清理不需要的分支

参考资源