Git Rebase 完全指南:从恐惧到精通

来源:https://www.brethorsting.com/blog/2026/01/git-rebase-for-the-terrified/

前言

作为 OneBusAway 项目的维护者,我经常要求贡献者在合并前对他们的分支进行 rebase。回应往往是犹豫甚至恐惧。我理解这种感受——rebase 在开发者社区中有着毁坏工作的恶名,网上的各种警告更加剧了这种恐惧。

但事实是:rebase 出错的最坏情况不过是删除本地克隆并重新开始。就这样。你的远程 fork 仍然存在,主仓库也还在。你总是可以恢复的。

既然消除了恐惧,让我来展示如何正确地使用 rebase。

为什么维护者要求 rebase

当你从 main 分支创建一个分支并工作几天后,main 分支仍在持续前进。其他 PR 被合并。当你的 PR 准备好时,你的分支历史已经与 main 产生了分歧。

虽然合并提交可以结合它们,但这会创建混乱的历史,提交交错在一起,使理解变更内容和原因变得更加困难。

Rebase 会将你的提交在当前的 main 分支上重放,就像你今天才创建这个分支一样。结果是清晰、线性的历史,更容易审查和 bisect(二分查找)以追踪 bug。

Rebase 前后对比

实际操作命令

第一步:配置上游仓库

首先,确保你已将上游仓库配置为 remote。如果你 fork 了一个仓库并克隆了你的 fork,你可能只有 origin 指向你的 fork:

git remote -v

如果没有看到主仓库,添加它:

git remote add upstream https://github.com/OneBusAway/onebusaway-ios.git

第二步:获取最新变更

从上游获取最新变更:

git fetch upstream

第三步:切换到特性分支

确保你在你的特性分支上:

git checkout your-branch-name

第四步:推送备份

在 rebase 之前,将当前工作推送到你的远程 fork。这为你提供了一个备份,以防出错时可以恢复:

git push origin your-branch-name

第五步:执行 rebase

现在将你的分支 rebase 到上游的 main 分支:

git rebase upstream/main

如果没有冲突,你就完成了 rebase。如果有冲突,Git 会停止并告诉你哪些文件需要处理。

理解冲突标记

当打开一个有冲突的文件时,你会看到类似这样的内容:

<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 10000;
>>>>>>> upstream/main

这在理解之前很困惑,但每个部分的意思是:

  • <<<<<<< HEAD======= 之间的内容是你的代码,来自正在重放的提交
  • =======>>>>>>> upstream/main 之间的内容是main 中的代码,与你的代码冲突

你的工作是决定最终代码应该是什么样的。有时你想要你的版本,有时是他们版本,有时是两者的结合。删除标记并只保留你想保留的代码。

推荐工具:我总是使用 VS Code 来完成这一步。它的合并冲突 UI 是我找到的最清晰的:它在每个冲突上方显示"接受当前更改"、"接受传入更改"、"接受两者更改"和"比较更改"按钮。你可以逐个点击冲突,无需手动查找标记。

当冲突变得棘手时

有些冲突是直接的:两个人以不同方式更改了同一行。选择一个或组合它们。

其他的则更困难。如果你在 rebase 多个提交,并且同一文件反复冲突,通常意味着你的更改以某种方式相互构建,无法干净地应用于新的基础。

处理策略

1. 先压缩,再 rebase

如果你有很多小提交,在 rebase 之前将它们合并成一两个逻辑提交。更少的提交意味着更少的冲突机会。

# 交互式 rebase 来压缩提交
git rebase -i HEAD~n  # n 是要压缩的提交数

2. 中止并尝试不同的方法

如果冲突太多,执行 git rebase --abort 并考虑你的分支是否分歧太远。有时从 main 创建新分支并手动重新应用你的更改会更容易。

git rebase --abort
git checkout main
git pull upstream main
git checkout -b new-branch-name
# 手动应用你的更改

3. 使用 git rerere

如果你发现自己反复解决相同的冲突,使用以下命令启用 rerere(重用记录的解决方案):

git config --global rerere.enabled true

Git 会记住你如何解决冲突,并在下次自动应用相同的解决方案。

解决冲突后

解决每个文件的冲突后:

git add path/to/resolved/file
git rebase --continue

重复直到 rebase 完成。如果出现问题并想要中止:

git rebase --abort

这会将你的分支恢复到开始前的状态。

Rebase 冲突处理流程

验证你的更改

rebase 后,验证你的更改仍然有效:

git log --oneline upstream/main..HEAD

这只显示你领先于 main 的提交。确保它们看起来正确。然后构建项目并运行测试。rebase 有时会导致微妙的问题,如果上游更改与你的工作冲突,而合并没有捕获到这些问题。

Force Push

这是人们紧张的地方。rebase 后,你的本地分支已经与远程分支分歧。正常的 git push 会失败。你需要 force push:

git push --force-with-lease origin your-branch-name

--force-with-lease 标志比 --force 更安全,因为如果有人在你上次获取后推送到你的分支,它会失败。这可以防止你意外覆盖其他人的工作。

重要:永远不要 force push 到 main 或任何共享分支。只对你自己的特性分支进行 force push。

当一切都出错时

如果你把事情搞砸了,无法弄清楚如何恢复,这里是"核选项":

  1. 将你想保存的任何工作推送到你的远程 fork(甚至到临时分支)
  2. 删除你的本地克隆
  3. 从你的 fork 重新克隆
  4. 再次添加上游 remote
  5. 重新开始 rebase 过程

这对我来说从未失败过。你的提交存在于 GitHub 上,直到你明确删除它们。你总是可以恢复。

重要注意事项

Rebase 会重写提交历史。这对于只有你工作的特性分支是没问题的。对于其他人基于其工作的分支,这不行。如果你在一个分支上与某人合作,在 rebase 之前协调,或者直接使用合并提交。

总结

一旦你理解了你总是可以恢复,rebase 就不再可怕了。最坏的情况是几分钟的重新克隆。好处是清晰的项目历史,更容易理解和维护。

核心要点

  1. 在 rebase 之前推送备份
  2. 使用 --force-with-lease 而不是 --force
  3. 永远不要 rebase 共享分支
  4. 理解冲突标记的含义
  5. 最坏情况:重新克隆,一切恢复

参考资料

最后修改:2026 年 01 月 14 日
如果觉得我的文章对你有用,请随意赞赏