Skip to content

分支与合并

本章要点

  • 分支的本质:一个可移动的"指针",所以创建分支极其廉价
  • 常用分支命令:list / create / switch / delete
  • 合并的两种方式:fast-forward 与 三方合并(merge commit)
  • 冲突的产生、查看与解决
  • 合并策略与常见陷阱

分支的本质

分支(branch)本质上只是一个指向某次提交的可移动指针。

text
        main


      ●───●───●         ← 每次提交,main 指针前移

创建一个新分支:

bash
$ git branch feature/login     # 新建分支(指针指向当前所在位置)
text
        main


      ●───●───●


        feature/login

创建分支 = 复制一个指针,不复制任何文件,所以极其廉价、瞬间完成。 "分支廉价"是 Git 鼓励"一功能一分支"的根本原因。

分支常用命令

bash
$ git branch                      # 列出本地分支(* 表示当前分支)
$ git branch -a                   # 列出所有分支(含远程 origin/xxx)
$ git branch -v                   # 显示每个分支的最新提交
$ git branch <>               # 创建分支
$ git switch <>               # 切换分支(推荐,语义清晰)
$ git checkout <>             # 切换分支(老命令,等价)
$ git switch -c <>            # 创建并切换(等价 git checkout -b)
$ git branch -d <>            # 删除分支(-D 强制删除未合并分支)
$ git branch -m <新名>          # 重命名当前分支
$ git branch -vv                  # 查看分支与上游跟踪关系

💡 git switch(Git 2.23+)是更明确的切换命令,避免 checkout 的多重含义(切换分支 / 恢复文件 / 检出提交)。

完整分支流程

bash
$ git switch main                 # 回到主分支
$ git switch -c feature/login     # 从 main 创建并切到新分支
# ...开发、提交...
$ git switch main                 # 切回 main
$ git merge feature/login         # 把 feature 合并进来
$ git branch -d feature/login     # 合并完成后删除特性分支

合并(Merge)

场景一:Fast-forward 合并(快进)

当被合并的分支没有分叉(main 自始至终没动过)时,Git 只需把 main 指针直接前移:

text
合并前:
      ●───●───●         main
              └───●───●  feature/login

合并后:
      ●───●───●───●───●  main(与 feature/login 重合)
bash
$ git merge feature/login
# Updating 3b1e7d0..8f4a2c9
# Fast-forward
  • 不产生新的合并提交,历史保持线性,非常干净。
  • 如果希望即使能快进也强制生成合并提交(保留"功能合并"痕迹),用 git merge --no-ff

场景二:三方合并(产生 merge commit)

当两个分支各自都有新提交(分叉了)时:

text
      ●───●───●            main

          └───●───●        feature/login
bash
$ git switch main
$ git merge feature/login
# Merge made by the 'ort' strategy.

Git 会比较三个位置:两个分支的共同祖先(merge base)、main、feature,然后合并出结果,生成一个合并提交(merge commit)

text
      ●───●───●───●  ← main(新的 merge commit,有两个父提交)
          │       ╱
          └───●───●    feature/login
bash
$ git log --oneline --graph     # 可以看到分叉合并的分支图
*   9c3a1f2 Merge branch 'feature/login'
|\
| * 8f4a2c9 feat(login): ...
* | 5d0e1a3 fix: ...
|/
* 3b1e7d0 ...

💡 --no-ff 在团队协作中常被强制使用(如 GitHub 的 "Merge pull request" 按钮),目的是在历史上保留"这是一个功能合入点"的节点。

合并冲突(Merge Conflict)

冲突是怎么产生的

当两个分支修改了同一个文件的同一处内容时,Git 无法自动判断该保留谁的,于是停下并报告冲突:

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

查看冲突

bash
$ git status
# both modified:   config.js     ← 冲突文件
$ git diff                    # 查看冲突详情

冲突标记长什么样

text
<<<<<<< HEAD
const timeout = 3000;          # ← 当前分支(HEAD)的内容
=======
const timeout = 5000;          # ← 被合并分支(feature/login)的内容
>>>>>>> feature/login
  • <<<<<<< HEAD:冲突块开始,以下是当前分支的版本
  • =======:分界线
  • >>>>>>> feature/login:以下是对方分支的版本

解决冲突的步骤

  1. 打开冲突文件,阅读两版内容。
  2. 决定保留哪边、或两边都要、或写全新的内容,删除所有冲突标记
  3. git add 标记为已解决。
  4. 提交(merge 场景下直接 git commit,Git 会带好默认的合并提交信息)。
bash
$ code config.js              # 编辑,删除 <<<<<<< ======= >>>>>>> 标记
$ git add config.js           # 标记为已解决
$ git commit                  # 完成合并

⚠️ 冲突标记(<<<<<<< 等)是需要你手动删除的文本,不是自动消失的。 ⚠️ 解决冲突时不要执行 git commit -a,确保所有冲突文件都 git add 过。 💡 如果合并到一半想放弃,git merge --abort 可回到合并前的状态。

解决冲突的辅助工具

bash
$ git mergetool               # 启动图形化合并工具(需先配置,如 Beyond Compare / meld)
$ git config --global merge.tool vscode   # 示例配置

合并策略一览

策略触发条件特点
fast-forward无分叉不产生 merge commit,历史线性
recursive / ort有分叉默认三方合并,产生 merge commit
ours / theirs特殊需求直接采用某一方内容
squash(不是策略而是选项)git merge --squash把对方所有提交压成一个提交,不保留来源历史
bash
$ git merge --squash feature   # 压缩合并(常用于整理 PR 历史)

分支管理的最佳实践

  1. 一功能一分支:分支应该短命,功能完成即合并、即删除。
  2. 主分支保持可发布main/master 上的代码任何时候都应该是能跑的。
  3. 频繁合并:分支存在越久,冲突概率越高、解决越痛。鼓励小步提交、小步合并。
  4. 合并前先更新git switch main && git pull 再合并,减少冲突。
  5. 命名规范feature/xxxfix/xxxhotfix/xxxrelease/xxx 前缀让分支一目了然。