惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

披萨盒的赛博日志

使用策略模式重构复杂业务分支 像 systemd 一样管理 MacOS 后台常驻任务 以ORM看封装的边界 修改Linux内核模块以支持WG OpenLDAP折腾日记 非特权模式容器 ssh 登录问题 在 Linux 开发环境中使用网络代理 白嫖 Aseprite 像素绘图软件 MongoDB 增删改查 Python数据分析工具包-Numpy 解决 CLion 中文乱码问题 搭建 RLCraft 服务器 SpringBoot读取配置文件 Centos 配置 LNMP 环境 部署项目时遇到的坑 浅谈 xhr 请求跨域问题 JavaScript 学习笔记 Eclipse配置Web开发环境 Vue2 基本知识 Ribbon 简单使用 Nacos 简单使用 Spring Cloud Alibaba 环境搭建 什么是RSS?什么是Feed?它们有什么关系? Docker基本使用 TensorFlow启用GPU加速 如何进行内网穿透 Hello World! Git基本使用 Butterfly常用标签外挂
Git Merge VS Git Rebase: 如何优雅地合并分支?
2025-04-10 · via 披萨盒的赛博日志

有关 Git 的常用基本操作,我在👉 Git 基本使用这篇文章里已经介绍过了,这里不多赘述。我们今天主要讲讲 Git Merge 与 Git Rebase 的基础概念与使用场景。

概念解释

虽说两者都可以用来将一个分支(为了表达清晰我们暂且称之为待合并分支)合并到另一个分支(我们称之为目标分支或当前分支),但设计理念却是完全不同的。我们首先介绍两者的基本概念以及使用方法,在随后的使用场景中我们再来比较两者的区别。

首先需要知道,Git Merge 有两种常见的合并方式。

快进合并(Fast-forward merge)

此合并方式发生的条件为目标分支自创建出待合并分支后没有任何提交。合并过程可以简单理解为目标分支将待合并分支的多出来的提交记录补到自己的后面,并移动自己的HEAD指针到最后一个提交记录。

特点:

  • 保留两个分支的提交记录,并且提交记录为线性
  • 最简单的一种情况,不会发生冲突
  • 合并后的最后一次提交记录为待合并分支的提交,合并操作本身不会创建新的提交记录

举个栗子,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后依次进行了提交 Add loginFix bugs

image-20250413110110202

最后在 main 分支上执行 git merge dev 合并 dev 分支。

image-20250413110315834

执行 git log --all --graph --oneline --decorate 查看拓扑结构如下(合并后):

image-20250412211625538

普通合并(Recursive merge)

此合并方式发生的条件为目标分支自创建出待合并分支后目标分支和待合并分支都有提交。可以理解为是快进合并到升级版本。

特点:

  • 保留两个分支的提交记录
  • 可能会发生冲突
  • 合并操作本身会创建一个新的合并提交记录

举个🌰,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后进行了提交 Fix bugs,与此同时,main 分支也创建了一个 Update 提交。

image-20250413110517213

最后在 main 分支上执行 git merge dev -m "Merge" 合并 dev 分支。

image-20250413111324080

执行 git log --all --graph --oneline --decorate 查看拓扑结构如下(合并后):

image-20250412211135154

除了知道这两种合并方式外,我们还要知道在合并过程中的冲突原因以及处理策略。

原因:当两个分支修改了同一文件的相同部分时,Git 无法自动合并,此时便会产生合并冲突。

解决步骤:

  1. 运行 git merge 后遇到冲突
  2. 使用 git status 查看冲突文件
  3. 手动编辑冲突文件(文件中有 <<<<<<<, =======>>>>>>> 标记),删除冲突标记并保留想要的代码
  4. 使用 git add <文件名> 标记冲突已解决
  5. 继续合并:git merge --continue
  6. 完成合并:git commit

如果不想合并了或者冲突无法解决,执行 git merge --abort 回到合并前的状态。

此外 git merge 命令本身还提供了一些常见的合并选项以及合并策略,我们这里简单介绍一下。

高级合并选项

使用方式为 git merge [option] branch_namegit merge --ff-only dev

  • --no-ff 强制创建新的合并提交,即使合并可以通过快进合并完成
  • --ff-only 仅允许快进合并,若无法快进则合并失败
  • --squash 将待合并分支的多个提交压缩为单个新提交,不保留待合并分支的提交历史
  • --no-commit 合并代码到目标分支的工作区但不提交,允许手动检查或调整

合并策略参数

使用 -s 指定合并策略,不加 -s 参数时默认为 recursive 策略。大多数情况下用默认策略就足够了,使用方式如 git merge -s recursive dev

常用合并策略有

  • recursive 基于递归三路合并算法,通过共同祖先、目标分支和待合并分支的提交进行三方合并,自动解决非冲突修改,冲突部分需手动处理
  • resolve 简化版的三路合并,仅寻找一个共同祖先,避免递归合并虚拟节点,可能减少自动合并但增加冲突概率
  • octopus 同时合并多个分支,如 git merge -s octopus feature1 feature2 feature3
  • subtree 将子目录作为独立仓库合并,适用于模块化项目或子仓库管理,如 git merge -s subtree module-folder
  • ours 完全保留当前分支内容,忽略待合并分支的所有修改(即使无冲突),仅记录待合并分支的提交历史

冲突解决策略

使用 -X 参数指定冲突解决策略,不加 -X 参数时默认不会应用任何冲突解决策略。使用方式如 git merge -X ours

常用的冲突解决策略有

  • ours 冲突时自动选择当前分支的代码,非冲突部分正常合并
  • theirs 冲突时自动选择待合并分支的代码,非冲突部分正常合并
  • ignore-space-change 忽略空格差异(如缩进或换行符),减少无意义冲突
  • patience 优化差异比对算法,更关注代码结构而非逐行匹配,提高合并准确性

综上,我们可以写出如下命令

1
git merge --no-ff -s recursive -X patience dev -m "Merge dev"

Git Rebase

Rebase(变基)本质上是将一系列提交从一个分支移动到另一个分支的顶端。注意:在变基操作中,"当前分支"通常为个人开发分支,如 dev 分支。细品接下来的 5 个步骤。

Git Rebase 都做了哪些事?

  1. Git 会先找到当前分支和待合并分支(如 main)的共同祖先提交,即分叉点
  2. 然后提取当前分支上所有分叉点之后的提交记录(保存为补丁)
  3. 丢弃当前分支分叉点之后的提交记录(第 2 步的补丁还在)
  4. 当前分支的顶端应用待合并分支分叉点之后的提交记录
  5. 在当前分支的顶端根据第 2 步的补丁创建一组全新的提交

还没明白?我们举个例子,场景如下:Dabby 从 main 分支的提交 Initial commit 拉取了 dev 分支,然后进行了提交 CD。与此同时,main 分支也创建了一个 A 提交。

image-20250413101041216

最后在 dev 分支上执行 git rebase main 变基 main 分支。

image-20250413101735284

因为是纯线性历史,这里就不放拓扑图了。以下是提交记录的线性关系表示。

变基前

main: Initial commit -> A

dev: Initial commit -> C -> D

image-20250413103522570

变基后

main: Initial commit -> A

dev: Initial commit -> A -> C' -> D'

image-20250413103351206

可以看到,1.变基后并不会修改待合并分支(main)。 2.变基后自身(dev)的提交历史发生改变。3.如果想让修改合并到 main 分支则必须切换到 main 分支后执行一次 Fast-forward 合并。

变基遇到文件冲突后我们可以解决:

  1. 运行 git rebase main 后遇到冲突
  2. 使用 git status 查看冲突文件
  3. 手动编辑冲突文件(文件中有 <<<<<<<, =======>>>>>>> 标记),删除冲突标记并保留想要的代码
  4. 使用 git add <文件名> 标记冲突已解决
  5. 执行 git rebase --continue 继续变基

或者执行 git rebase --abort 取消变基。

有个有意思的事,git rebase 命令还有一个 -i/--interactive 参数,可以开启交互式变基模式,允许用户编辑提交历史(如合并提交、修改提交信息、调整顺序等)。

使用场景

通过上面的学习,我们对 Git Merge 和 Git Rebase 的基础概念以及合并方式有了一个大致的了解,接下来我们需要了解他们的区别以及在各种场景下应该使用什么方式。

一张表格总结它们的核心区别:

对比维度 Git Merge Git Rebase
合并方式 创建新的合并提交,保留两个分支的提交历史 将当前分支的提交应用到待合并分支的最新提交后
历史记录 保留原始提交记录 重写历史
适用分支 公共分支(如main)以及需要保留合并历史的分支 个人开发分支或本地分支
冲突处理 合并提交时一次性解决冲突 变基过程中逐个提交解决冲突(可能多次)
是否线性 不一定为线性 一定为线性
典型操作 git switch main git merge dev git switch dev git rebase main

综上所述:

  • 一般无特殊需求的情况下都用 git merge 是最稳妥的选择。
  • 多人协作下的公共分支(如 main 分支)必须git merge千万不要在公共分支上执行变基操作。
  • 个人项目或者本地项目想用啥用啥。

优雅,实在是优雅。