Git 真正难的地方并不是记住几个命令,而是理解「分支为什么存在」「Merge 和 Rebase 到底有什么区别」以及「出现冲突以后应该怎么处理」。

日常开发中,一个项目通常同时存在多个功能开发、Bug 修复和版本发布任务。如果所有人都直接修改同一个分支,很快就会出现代码互相覆盖、提交历史混乱以及难以追踪的问题。

Git 的分支机制解决了这个问题。开发者可以从主分支创建独立分支,在自己的分支上完成开发,然后通过 Merge 或 Rebase 将代码重新整合到目标分支。

本文不从 Git 的基础命令开始,而是直接从实际开发流程理解 Git 分支、Merge、Rebase 和冲突处理。

Git 分支到底是什么

Git 中的分支本质上是指向某个 Commit 的引用。

例如项目最开始只有一条 main:

A --- B --- C
              ↑
             main

开发新功能时创建 feature/login:

A --- B --- C
              ↑
             main
              ↑
        feature/login

继续开发后:

A --- B --- C --- D --- E
              ↑           ↑
             main    feature/login

此时 main 并没有发生变化,feature/login 在自己的提交历史上继续向前发展。

Git 官方文档也强调,Git 分支非常轻量,创建分支本质上只是创建一个指向 Commit 的引用,因此 Git 鼓励开发者频繁使用分支。

这也是 Git 分支和一些传统版本控制系统最大的区别之一。

创建和切换分支

现代 Git 推荐使用 git switch:

git switch -c feature/login

它相当于创建并切换到:

feature/login

查看当前分支:

git branch

切换到已有分支:

git switch main

查看所有分支:

git branch -a

查看分支对应的最新 Commit:

git branch -v

如果项目使用较新的 Git 版本,建议优先使用 switch 表达分支切换,而不是继续把 checkout 用作所有场景的通用命令。


一个真实的多人开发场景

假设项目目前只有:

main

现在同时出现两个需求:

feature/login
feature/payment

于是:

                 ┌── feature/login
                 │
A --- B --- C ---┤
                 │
                 └── feature/payment
                 ↑
                main

两个开发者分别在自己的分支工作。

登录功能产生:

A --- B --- C --- D --- E
              ↑           ↑
             main    feature/login

支付功能产生:

A --- B --- C --- F --- G
              ↑           ↑
             main   feature/payment

此时 main 并不知道 D、E、F、G。

当功能完成后,就需要把这些变化重新整合到 main。

这就是 Merge 和 Rebase 要解决的问题。


Merge:把两个开发历史合并起来

Merge 的目标非常直接:

把一个分支的开发历史合并到当前分支。

例如:

A --- B --- C --- D --- E
              \         ↑
               F --- G --┘

现在位于 main:

git switch main

执行:

git merge feature/login

如果两个分支已经产生了不同的提交,Git 可能创建一个新的 Merge Commit:

A --- B --- C --- D --- E
              \         \
               F --- G --- M

其中:

M

就是 Merge Commit。

它有两个父 Commit:

E
G

Git 官方将 merge 定义为「Join two or more development histories」,也就是将多个开发历史连接起来。

Fast-forward Merge

并不是每一次 Merge 都会产生 Merge Commit。

例如:

A --- B --- C --- D
              ↑
             main
                  ↑
             feature

如果 main 没有产生新的提交,那么:

git switch main
git merge feature

Git 可以直接把 main 指向 D:

A --- B --- C --- D
                  ↑
              main/feature

这就是 Fast-forward Merge。

因为两个分支之间没有真正产生分叉,所以不需要创建额外的 Merge Commit。Git 官方文档也明确说明了 Fast-forward 的这种行为。

如果希望无论如何都创建 Merge Commit,可以使用:

git merge --no-ff feature/login

形成:

A --- B --- C -------- M
              \       /
               D --- E

在团队开发中,--no-ff 有一个实际价值:可以让 Git 历史明确保留「这个功能分支曾经被合并进来」这一信息。


Rebase:重新整理提交历史

Merge 是「把两个历史合并」。

Rebase 的思路则不同:

把当前分支的提交重新应用到另一个基础之上。

假设现在:

A --- B --- C --- D --- E
              \
               F --- G

其中:

main:    A-B-C-D-E
feature: A-B-C-F-G

在 feature 上执行:

git rebase main

Git 会把 F、G 的修改重新应用到 E 后面。

结果类似:

A --- B --- C --- D --- E --- F' --- G'
                              ↑
                           feature

注意:

F != F'
G != G'

虽然代码修改可能相同,但它们是新的 Commit。

Git 官方文档对 Rebase 的描述也是「重新应用提交」:Git 会找出当前分支相对于上游分支的提交,然后依次重新应用这些提交。

为什么需要 Rebase

最大的价值是让提交历史更加线性。

使用 Merge:

A --- B --- C --- D --- M
              \         /
               E --- F

使用 Rebase:

A --- B --- C --- D --- E' --- F'

对于需要频繁同步主分支的功能开发来说,Rebase 可以减少大量没有实际业务意义的 Merge Commit。

例如:

git switch feature/login
git fetch origin
git rebase origin/main

然后继续开发。


Merge 和 Rebase 到底怎么选

这是 Git 开发中最容易产生争议的问题。

可以简单理解为:

场景更适合
合并已经完成的功能分支Merge
保留真实分支拓扑Merge
希望历史保持线性Rebase
本地开发分支同步最新 mainRebase
已经共享给其他人的公共分支谨慎 Rebase
希望避免修改已有公共历史Merge

最重要的一条原则是:

不要随意 Rebase 已经被其他人使用的公共分支。

因为 Rebase 会创建新的 Commit。

例如:

A --- B --- C --- D
              \
               E --- F

别人已经基于 F 开发:

F --- G --- H

如果你对公共分支进行 Rebase,F 可能被重新写成 F':

A --- B --- C --- D --- E' --- F'

此时:

G
H

依赖的历史已经发生变化。

这也是 Git 官方文档提醒 Rebase 需要谨慎使用的原因。


实际开发中推荐的分支流程

对于普通 Web 项目,可以采用比较简单的模型:

main
 │
 ├── feature/login
 │
 ├── feature/payment
 │
 └── fix/user-login

开发功能时:

git switch main
git pull --ff-only

git switch -c feature/login

开发完成后:

git add .
git commit -m "feat: add user login"

在提交 Pull Request 之前,先同步主分支:

git fetch origin
git rebase origin/main

如果没有冲突:

main
  \
   A --- B --- C

最终通过 Pull Request 合并。

如果团队希望保留功能分支的完整边界,也可以采用:

git merge --no-ff

关键不是强制所有项目使用一种方式,而是团队必须统一规则。


Git 冲突是怎么产生的

冲突通常发生在两个分支修改了 Git 无法自动判断如何合并的相同内容。

例如:

main:

$name = 'Leanku';

feature/user:

$name = 'Tom';

两个分支分别修改了同一行。

此时:

git merge feature/user

Git 可能无法判断应该保留哪个版本:

<<<<<<< HEAD
$name = 'Leanku';
=======
$name = 'Tom';
>>>>>>> feature/user

这就是 Merge Conflict。

Git 的三方合并机制会尝试根据共同祖先、当前分支和被合并分支进行合并;无法自动判断的部分才需要开发者处理。


Merge 冲突的正确处理流程

发生冲突后,首先查看状态:

git status

可能看到:

both modified: src/User.php

打开文件:

<<<<<<< HEAD
当前分支内容
=======
其他分支内容
>>>>>>> feature/user

根据业务需求修改成最终版本:

$name = 'Leanku';

然后告诉 Git:

git add src/User.php

检查:

git status

最后完成 Merge:

git commit

或者:

git merge --continue

如果发现这次 Merge 不应该继续:

git merge --abort

Git 官方文档明确提供了 merge --continue 和 merge --abort 用于处理暂停中的 Merge。


Rebase 冲突怎么处理

Rebase 的处理方式和 Merge 类似,但过程更容易让初学者迷惑。

执行:

git rebase origin/main

如果出现冲突:

CONFLICT (content): Merge conflict in ...

首先:

git status

解决冲突文件。

然后:

git add .

继续:

git rebase --continue

如果后面还有冲突,就重复:

解决冲突
    ↓
git add
    ↓
git rebase --continue
    ↓
继续处理下一个冲突

如果发现整个 Rebase 不应该继续:

git rebase --abort

Git 官方文档同样提供了 rebase --continue、rebase --abort 等操作。


如何减少 Git 冲突

真正优秀的 Git 使用方式不是「冲突以后解决得很快」,而是尽量减少不必要的冲突。

第一,功能分支不要长期存在

不推荐:

feature/login
      ↓
开发 2 个月
      ↓
最后一次性合并

更推荐:

创建分支
   ↓
完成一个小功能
   ↓
提交
   ↓
Pull Request
   ↓
Merge

分支生命周期越长,与 main 产生差异的可能性越大。

第二,及时同步主分支

开发期间可以定期:

git fetch origin
git rebase origin/main

这样可以尽早发现冲突,而不是等到功能完成之后才一次性解决。

第三,Commit 不要过于庞大

不推荐:

feat: 完成整个用户系统

一个 Commit 修改几百个文件,最终很难 Review,也容易产生冲突。

更推荐:

feat: add user repository
feat: add user service
feat: add user controller
test: add user service tests

每个 Commit 尽量表达一个相对独立的变化。


git pull 到底使用 Merge 还是 Rebase

很多 Git 冲突实际上不是来自手动执行 merge,而是来自:

git pull

git pull 本质上先执行 Fetch,再将远程变化整合到当前分支。

根据配置,可以采用不同方式:

git pull --rebase

或者:

git pull --no-rebase

也可以强制只接受 Fast-forward:

git pull --ff-only

Git 官方文档明确支持这几种整合方式。

个人开发环境中,如果希望提交历史保持干净,可以考虑:

git config --global pull.rebase true

如果团队希望明确禁止 Pull 自动创建 Merge Commit,也可以使用:

git config --global pull.ff only

但具体选择应该以团队协作规范为准,而不是每个人自行配置一套规则。


一套推荐的实际工作流

对于普通 PHP、Java、Go、Node.js 等 Web 项目,可以采用下面的流程:

                main
                  │
                  │
            创建 feature
                  │
                  ▼
          feature/login
                  │
             开发 + Commit
                  │
                  ▼
          同步 origin/main
                  │
               Rebase
                  │
                  ▼
             Pull Request
                  │
             Code Review
                  │
                  ▼
               Merge
                  │
                  ▼
                 main

对应操作:

# 1. 更新主分支
git switch main
git pull --ff-only

# 2. 创建功能分支
git switch -c feature/login

# 3. 开发并提交
git add .
git commit -m "feat: add user login"

# 4. 同步主分支
git fetch origin
git rebase origin/main

# 5. 推送功能分支
git push -u origin feature/login

# 6. 创建 Pull Request

如果 Rebase 修改了已经推送过的个人功能分支,通常需要:

git push --force-with-lease

而不是:

git push --force

--force-with-lease 能够在一定程度上避免覆盖远程分支上其他人的新提交。


最容易犯的几个错误

直接在 main 上开发

git switch main
# 直接开始写代码

个人实验项目问题不大,但团队项目应该避免。

把 Merge 和 Rebase 当成完全相同的操作

两者都可以整合代码,但对 Commit 历史的影响完全不同。

Merge
A---B---C-------M
     \         /
      D---E---/

Rebase
A---B---C---D'---E'

Merge 保留分叉历史,Rebase 会重新构造提交历史。

Rebase 公共分支

这是最危险的习惯之一。

尤其不要随意对:

main
develop
release/*

等已经被团队共享的分支执行 Rebase。

使用 --force 覆盖远程分支

如果确实需要强制更新已经 Rebase 的个人分支:

git push --force-with-lease

通常比:

git push --force

更加安全。


Git 分支管理的核心原则

真正需要记住的不是几十个命令,而是下面几条原则:

第一,功能开发使用独立分支。

main
 ├── feature/A
 ├── feature/B
 └── fix/C

第二,Merge 用于整合开发历史。

git merge feature/login

第三,Rebase 主要用于整理自己的开发历史和同步上游分支。

git rebase origin/main

第四,不要随意修改公共历史。

尤其是已经被其他开发者使用的分支。

第五,冲突不可怕,真正危险的是不了解当前 Git 操作处于什么状态。

遇到问题首先:

git status

然后根据当前状态决定:

git merge --abort
git merge --continue

git rebase --abort
git rebase --continue

而不是盲目执行 reset --hard。

总结

Git 分支的核心价值不是「多几个分支」,而是让不同开发任务能够相互隔离。

Merge 和 Rebase 则代表两种不同的历史整合方式:

Merge
    保留真实分支历史
    ↓
    适合整合团队开发结果

Rebase
    重新整理提交历史
    ↓
    适合个人分支同步和历史整理

在实际团队开发中,可以采用一个简单原则:

公共历史优先保持稳定,个人历史可以整理;功能分支及时同步,完成后尽快合并。

掌握 Branch、Merge、Rebase 之后,Git 真正值得学习的下一部分就是「出了问题怎么恢复」。下一篇将集中解决 reset、restore、revert、stash、reflog 这些最容易混淆、但在实际开发中非常重要的操作。

官方参考