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

推荐订阅源

The Register - Security
The Register - Security
云风的 BLOG
云风的 BLOG
U
Unit 42
F
Fortinet All Blogs
The GitHub Blog
The GitHub Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
Vercel News
Vercel News
S
Security @ Cisco Blogs
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
I
Intezer
MongoDB | Blog
MongoDB | Blog
AI
AI
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
Y
Y Combinator Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
W
WeLiveSecurity
博客园 - 叶小钗
S
SegmentFault 最新的问题
N
News | PayPal Newsroom
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
DataBreaches.Net
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
Spread Privacy
Spread Privacy
H
Help Net Security
美团技术团队
博客园 - 司徒正美
T
Threat Research - Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
Kaspersky official blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
TaoSecurity Blog
TaoSecurity Blog
N
Netflix TechBlog - Medium
L
Lohrmann on Cybersecurity
J
Java Code Geeks
量子位
Martin Fowler
Martin Fowler
博客园_首页

李锋镝的博客

LiteLLM 本地代理搭建 Claude-HUD 使用文档 Kratos+ —— Kratos 主题二次开发记录 译文:如何将单体应用拆解为微服务 codebase-memory-mcp 极简完整使用指南 Claude Haiku 4.5、Claude Sonnet 4.6、Claude Opus 4.7 区别以及各自的新特性 SchedulingConfigurer详解 踩坑60+次后,我终于搞懂 Claude Skill 怎么写才会真的触发 Everything Claude Code 详细使用文档 配置Jackson使用字段而不是getter/setter来序列化和反序列化 这个域名注册整整十年了,十年时间,真快啊 Claude Code全维度实战指南:从入门到精通,解锁AI编程新范式 Apollo配置中心中的protalDB的作用是什么 org.apache.ibatis.plugin.Interceptor类详细介绍及使用 岁末 Excel2016右键新建工作表,打开时提示“因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配。”的解决办法 wordpress增加说说功能 Java 为什么有这么多 “O”? 别再背线程池的七大参数了,现在面试官都这么问 2024年11月1号 农历十月初一 我的第一个WordPress插件:Dylan Custom Plugin上线了 推荐一款比较养眼的Xshell配色方案 hnswlib installation failed 开工啦~ 阳了... MybatisCodeHelperPro激活 @Async注解的坑 新买的笔记本发货啦…… 这个中秋节感觉过的好累啊 IDEA下载源码报:Cannot connect to the Maven process. Try again later. RocketMQ的push消费方式实现详解 看病难~取药难~~ 笑死、腹肌……根本不可能有腹肌的~~ 居家办公了~ C# 11 的这个新特性,我愿称之最强! IntelliJ IDEA 2020.3.x永久白嫖(Windows/Mac) IDEA无限试用方法【2020.3最新亲测有效】 SpringBoot使用注解的方式构建Elasticsearch查询语句,实现多条件的复杂查询 UUID太长怎么办?快来试试NanoId 忽然发现,在校大学生可以免费领一年有道云笔记会员~ 醒醒~补个税了 使用itext和freemarker来根据Html模板生成PDF文件,加水印、印章 来来来,用python画一个冰墩墩儿 SpringBoot整合GraphQL入门教程 办理居住证困难重重啊! 妈呀,昨天晚上睡觉做了一晚上的梦,可累死我了 感觉Typecho很简洁啊…… WordPress的自动更新好烦啊 BeanCopier工具类(性能优化工具类) 内存屏障浅析 居住证可算是申请通过了…… 试了下壁挂炉供暖 关于重阳节 居住证签注... Spring Boot 2.x使用PostgreSQL数据库 十一节后开工头一天,修了个耳机…… 玉楼春·尊前拟把归期说 nginx反向代理配置去除前缀 1931→2021! Navicat Premium数据库账号密码解密 百度的索引量数据现在有点儿太夸张了吧? 哇塞~这个小姐姐实在太惊艳了…… 关于8月29号下午博客故障的一些记录 如何形象的描述反应式编程中的背压(Backpressure)机制? 基于Java8的Either类 海琴烟~~~ JMX监控权限认证配置 IntelliJ IDEA 2019.3.3 永久激活 破解[Windows] SpringBoot整合Elasticsearch详细步骤以及代码示例(附源码) SpringBoot整合Elasticsearch游标查询(scroll) GitLab创建新项目,初次提交命令和流程 吐槽下Google浏览器~ 从零搭建Spring Cloud Gateway网关(一) 从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志 博客有logo啦 你的答案 数据库事务的一点简单总结 Spring Boot发展史(Spring Boot介绍) 【漫画】戏说外行对程序员的误会有多深 【收藏】从面试官角度观察到的程序员技能瓶颈,同时给出突破瓶颈的建议 Git中.gitignore文件不起作用的解决办法 背影 - 朱自清 jmap命令(jdk1.8) 彻底搞懂mysql日志系统binlog,redolog,undolog 出院了~~~ linux中ftp查看不到文件列表的问题 PHP版本怎么更新啊…… 成语接龙 StarUML4.0破解文件 Java SPI详解 WordPress评论框增加自定义表情 因在公司上不正经网站,我没过试用期… 优化了MYSQL大量写入问题,老板奖励了1000块给我 不慌不忙的坚强(林徽因39段最美文字!) ConcurrentHashMap常用方法源码解析(jdk1.8) 我是如何失去团队掌控的? HBASE填坑日志 关闭apache httpclient4.5 DEBUG日志 使用OpenShift搭建k8s集群 SonarQube Scanner的配置与使用简介
一次 Git Rebase 事故,让我彻底明白 Rebase 和 Merge 的区别
李锋镝 · 2025-10-17 · via 李锋镝的博客

前言

事情的起因是这样的:上周我在项目里用 git rebase 整理分支,结果把同事的提交“消失”了,虽然最后通过数据恢复救了回来,但整个过程大家都吓出一身冷汗。这次惊险的事故,让我不得不沉下心去深究 rebasemerge 的底层差异,以及它们在不同协作场景下的正确用法——毕竟“知其然更知其所以然”,才能避免再踩类似的坑。

一、先还原“事故现场”

要理解问题的根源,先从当时的分支场景说起:

我们项目的分支结构很简单:

  • main 分支是主分支,同事在上面修复了一个紧急 bug,提交历史是 A → B → C(其中 C 是 bug 修复提交);
  • 我在 feature 分支开发新功能,基于 mainB 提交开始,提交了 DE 两个功能代码,分支关系如下:
    main:    A --- B --- C (bug 修复)
           \
    feature:    D --- E (你的新功能)

当时我想把 main 分支的 bug 修复同步到 feature 分支,觉得 rebase 能让提交历史更整洁,就直接执行了以下命令:

git checkout feature  # 切换到自己的feature分支
git rebase main       # 基于main分支做rebase

执行后,分支历史确实变成了“直线”,feature 分支的 DE 提交被“搬到”了 main 最新的 C 提交之后,看起来很清爽:

main:    A --- B --- C
                        \
feature:                 D' --- E'  # D'、E'是rebase后生成的新提交(ID已改变)

但问题出在:我执行 rebase 前,没有先拉取远程 main 分支的最新代码(当时本地 main 分支还是旧的 B 提交),且在解决冲突时误删了同事 C 提交里的代码。更糟的是,我直接用 git push -f 强制推送了 feature 分支到远程,导致远程分支的历史被覆盖——同事的 bug 修复提交 C,在远程仓库里“凭空消失”了。

事故后我翻遍了 Git 官方文档,也对比了两种命令的实际执行效果,终于理清了它们的核心差异:本质是“是否修改提交历史”,以及“如何处理分支合并”。

1. git merge —— 历史完整,简单安全

merge 的核心逻辑是“保留分支历史,创建新的合并提交”,它不会改动任何已有的提交记录,相当于在两个分支的最新节点之间“架一座桥”。

执行命令

比如要把 main 分支的更新同步到 feature 分支:

git checkout feature  # 切换到需要接收更新的分支
git merge main        # 合并main分支的内容

分支历史变化

合并后会生成一个新的“merge commit”(通常标记为 M),分支历史会保留原来的“分叉”结构:

A --- B --- C ------ M (merge commit,新生成)
 \              /
  D --- E -----        # feature分支的原有提交不变

核心特点

  • 不修改历史:所有原有提交(ABCDE)的 ID 和内容都不会变,绝对安全;
  • 冲突处理简单:即使有代码冲突,只需要在创建 merge commit 时解决一次,后续不会再重复出现;
  • 历史可追溯:从提交记录能清晰看到“哪个分支在什么时候合并了什么内容”,适合团队协作追溯问题。

2. git rebase —— 历史干净,但会改动历史

rebase 的核心逻辑是“改写提交历史,让分支看起来像直线开发”,它会把当前分支的所有提交,“重新基于”目标分支的最新提交排列,相当于“擦掉”原来的提交记录,再重新生成新的提交(即使内容相同,提交 ID 也会改变)。

执行命令

同样是同步 main 分支到 feature 分支:

git checkout feature  # 切换到自己的分支
git rebase main       # 基于main的最新提交,重排feature的提交

分支历史变化

feature 分支的 DE 提交会被“移动”到 main 最新的 C 提交之后,生成新的 D'E' 提交,最终历史变成一条直线:

A --- B --- C --- D' --- E'  # 没有分叉,历史整洁

核心特点

  • 修改历史:原有提交(DE)会被替换为新提交(D'E'),提交 ID 改变——这是最危险的点;
  • 冲突处理复杂:如果多个提交都和目标分支有冲突,需要逐个提交解决(比如 D 冲突解决完,还要处理 E 的冲突);
  • 历史整洁:分支记录没有“分叉”,看起来像是从目标分支直接开发的,适合整理个人分支的提交记录。

三、各自的安全使用场景

搞清楚差异后,关键是要知道“什么时候该用哪个”——选对场景,才能既发挥它们的优势,又避免风险。我整理了一张场景对比表,覆盖了日常开发中最常见的情况:

场景 推荐方式 原因
本地分支开发,还没 push 到远程 rebase 本地分支的提交只属于自己,用 rebase 整理历史(比如合并零散的“修复bug”提交),后续合并到主分支更清晰
拉取远程更新,不想生成多余 merge commit pull --rebase git pull --rebase origin main 替代 git pull,避免每次拉取都生成一个无用的 merge commit,保持本地历史整洁
分支已 push 到远程,且有同事基于它开发 merge 如果用 rebase 改写历史后强制推送,会导致同事的本地分支与远程冲突,甚至丢失代码
大型团队的主分支(如 main、dev)合并 merge 主分支需要保留完整的合并记录,方便追溯“谁在什么时候合并了什么功能”,出问题时能快速定位
个人项目或短期临时功能分支 rebase 个人项目不需要复杂的历史追溯,用 rebase 保持提交记录简洁,后续维护更轻松

四、如何避免 rebase 导致代码丢失?

这次事故让我总结出几个“rebase 安全守则”,只要遵守这些规则,就能大幅降低风险:

  1. 绝对不在公共分支上用 rebase
    公共分支(如 maindev)是所有人协作的基础,一旦用 rebase 改写历史,会导致整个团队的分支混乱。如果需要同步公共分支的更新,用 merge 而非 rebase

  2. rebase 前先拉取远程最新代码
    执行 rebase 前,一定要先通过 git fetch 获取目标分支的最新状态,避免基于旧版本做 rebase(这是我这次踩的核心坑):

    git fetch origin  # 拉取远程所有分支的最新代码
    git rebase origin/main  # 基于远程main的最新版本做rebase,而非本地旧版本
  3. 解决冲突时逐行检查,不盲目删除
    rebase 冲突时,Git 会提示“哪些文件有冲突”,打开文件后,一定要仔细对比冲突部分的代码——尤其是同事的提交,确认无误后再保留,避免手滑删错代码。

  4. rebase 出错时,立即用 --abort 回滚
    如果在 rebase 过程中发现不对劲(比如冲突太多,或者误删了代码),只要还没执行 git rebase --continue,就能用以下命令彻底回滚到 rebase 前的状态:

    git rebase --abort  # 紧急回滚,救了我好几次
  5. 推送前检查提交历史和差异
    推送分支前,先用以下命令确认提交历史是否正确,代码差异是否符合预期:

    git log --oneline --graph --decorate  # 查看分支历史的图形化结构
    git diff origin/feature  # 对比本地feature分支和远程feature分支的差异

五、我的经验总结

经过这次事故,我对 mergerebase 有了更通俗的理解:

  • merge 像是在账本上“追加记录”——不管之前的记录怎么分,新记录会明确写清“合并了哪部分内容”,不动旧账,安全可靠,但账本可能会有点乱;
  • rebase 像是在“修改旧账本”——把之前的记录重新整理成整齐的顺序,账本变干净了,但如果改的时候不小心擦错了,旧记录可能就找不回来了。

最后想分享一句心得:Git 是个很宽容的工具(很多操作都能回滚),但对“提交历史”的操作必须心存敬畏。代码丢失不可怕,可怕的是不知道为什么会丢。只有真正理解 mergerebase 的底层逻辑,才能在团队协作中既保持高效,又避免给同事“挖坑”。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/others/4518