- Merge 用一个新的合并提交连接两个分支,并保持历史的原貌。
- Rebase 把你的提交在另一个分支之上重放,得到一条直线,但提交哈希会变。
- 永远不要对别人已经拉取的提交执行 rebase。
- 在发起拉取请求之前对自己的功能分支做 rebase,把它并入主分支时使用 merge。
你的功能分支落后于 main,而你需要新的改动。Git merge 与 rebase 就是获取这些改动的两种方式之间的选择,两者都是正确的,区别在于之后的历史长什么样。
我们建了一个很小的仓库,运行了两个命令,并把真实输出贴在下面,让你比较结果而不是凭空想象。刚接触 Git?请先看我们的 Git 入门教程(英文)。
git merge 和 git rebase 分别做什么
git merge 取两个分支的最新提交并把它们合并。如果两个分支都有新提交,Git 会创建一个有两个父提交的合并提交,已存在的内容不会被改动。
git rebase 取出你分支独有的提交,先放在一边,把你的分支移到另一个分支的最新提交上,然后逐个重放你的提交。被重放的提交是带有新哈希的新提交,尽管改动内容相同。
git merge 实战
起点如下:main 上有提交 A,feature 上有两个提交(F1、F2),之后 main 又多了一个提交(M1)。现在把 main 合并进 feature。
git switch feature
git merge main
git log --oneline --graph
* f507aab Merge main into feature
|\
| * 8842fb5 M1
* | 082cc6a F2
* | 5a71e3c F1
|/
* 8766865 A
图中展示了真实的过程:两条工作线被合并在一起。原来的提交 F1 和 F2 保留各自的哈希。代价是多了一个合并提交,而在活跃的项目里,这张图会变成一张网。
git rebase 实战
起点相同,但这次我们把 feature 变基到 main 上。
git switch feature
git rebase main
git log --oneline --graph
* 1ff474b F2
* bf9149e F1
* 8842fb5 M1
* 8766865 A
历史成了一条直线,就像你是在 M1 之后才开始工作的。注意哈希:F1 原来是 5a71e3c,现在是 bf9149e;F2 从 082cc6a 变成了 1ff474b。Git 重写了它们,这就是 rebase 需要小心的原因。
merge 与 rebase 并排对比
| 方面 | Merge | Rebase |
|---|---|---|
| 历史形状 | 带合并提交的分叉图 | 一条直线 |
| 已有提交 | 保持不变 | 以新哈希重写 |
| 冲突 | 在合并提交中一次性解决 | 每个被重放的提交都可能需要处理 |
| 在共享分支上是否安全 | 安全 | 不安全 |
| 最适合 | 把完成的工作并入 main | 评审前更新自己的分支 |
提示:拿不定主意时,问问还有谁拥有你的分支。没有人的话,rebase 是安全的;如果同事已经拉取了它,就用 merge。
rebase 的黄金法则
不要对存在于你自己仓库之外的提交做 rebase。rebase 之后,你的分支与服务器上的副本不再一致,所以推送需要强制。如果同事在旧提交的基础上做了工作,他们的历史就会与你的产生分歧,处理起来很痛苦。
对自己的私有功能分支做 rebase 没问题;对 main 或其他人在用的分支做则不行。
安全的团队工作流
这套流程能保持历史整洁,又不会让任何人意外。大多数团队在 CI 流水线前也这么做,例如我们的 GitHub Actions CI/CD 指南(英文)里的那个。
git switch feature
git fetch origin
git rebase origin/main # or: git pull --rebase
# fix conflicts, then: git add <file> && git rebase --continue
git push --force-with-lease # only on your own branch
如果自上次 fetch 以来有人向服务器分支推送过,--force-with-lease 会拒绝覆盖它,而普通的 --force 会悄悄把它毁掉。拉取请求通过后,把它合并进 main。
如何撤销错误的 rebase
rebase 看起来有破坏性,但旧提交会在 Git 的 reflog 中保留一段时间,所以你可以退回去。
git reflog # find the commit before the rebase
git reset --hard HEAD@{5} # example position, check reflog first
在任何有风险的 rebase 之前,用 git branch backup 创建一个备份分支,恢复就只需一条命令。旁边还要在容器里运行数据库或工具?请看我们的 Docker 入门教程(英文)。
总结
Merge 保留真相,rebase 保持整洁。用 rebase 刷新自己的分支,用 merge 交付它,并且永远不要改写别人依赖的历史。
常见问题
git merge 和 git rebase 有什么区别?
Merge 用一个合并提交合并两个分支,并保留所有已有提交。Rebase 把你的提交移到另一个分支之上并重写它们,从而得到线性的历史。
我该用 merge 还是 rebase?
对自己的功能分支用 rebase 保持最新,把它交付到 main 这样的共享分支时用 merge。只要其他人共享该分支,就用 merge。
git rebase 危险吗?
在私有分支上是安全的,在共享分支上有风险,因为它会重写提交哈希。出问题时,可以用备份分支和 reflog 来恢复。
force-with-lease 是做什么的?
只有当远程分支仍在你上次看到的位置时才强制推送,因此不会意外覆盖同事的新提交。
rebase 会删除我的提交吗?
不会。它会创建内容相同的新提交,旧提交会留在 reflog 中,直到 Git 清理它们。
继续学习(英文文章)
需要测试、WordPress 或 SEO 方面的帮助?
我们帮助团队交付更好的软件并让网站成长。告诉我们你在做什么,我们会给出清晰的下一步建议。
更多指南 测试、人工智能和现代开发
喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。
人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它
人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。
阅读文章 →DevOps环境变量解释:.env 文件、秘密和配置正确完成
使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。
阅读文章 →软件开发API 速率限制解释:算法、标头和处理 429 错误
API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。
阅读文章 →测试自动化Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误
使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。
阅读文章 →软件测试代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义
用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。
阅读文章 →数据库与 SQLSQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们
DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。
阅读文章 →数据库与 SQLSQL 视图解释:创建、使用和避免陷阱(附示例)
SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。
阅读文章 →软件开发TypeScript 接口与类型:真正的差异,使用编译器进行测试
TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。
阅读文章 →软件开发Python *args 和 **kwargs 解释:灵活的函数和清晰的示例
Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。
阅读文章 →软件开发Python Lambda 函数:语法、实际用途以及何时不使用它们
Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。
阅读文章 →人工智能什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环
什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。
阅读文章 →DevOps监控与可观察性:真正的区别,包括日志、指标和跟踪
监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。
阅读文章 →DevOpsDocker Volumes 与 Bind Mounts:以正确的方式保留容器数据
Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。
阅读文章 →测试自动化TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式
TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。
阅读文章 →软件开发Git merge 与 rebase 的区别:示例及如何选择
用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。
阅读文章 →软件开发Python 列表推导式:语法、示例与何时避免使用
Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。
阅读文章 →数据库与 SQLSQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)
通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。
阅读文章 →