变基
1. 变基的由来
- 回顾之前分支合并
-
-
分叉的提交历史
编辑
-
通过合并操作来整合分叉的历史
编辑
-
- 有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
2. 变基的基本操作
- 切换到想要变基的分支:
git checkout experiment
- 变基:
git rebase master
3. 变基的原理
编辑
- 确定共同祖先:Git 首先寻找
experiment
分支和master
分支的共同祖先提交,此例中为C2
,因为experiment
分支从C2
处分叉。 - 保存提交序列:Git 把
experiment
分支上从共同祖先C2
之后的提交(即C4
)保存成一个序列。这些提交记录了experiment
分支相对共同祖先的变化。 - 调整分支基础:Git 将
experiment
分支的指针移开,把它的基础设置为master
分支的最新提交C3
。这一步为后续重新应用提交做准备,让experiment
分支基于master
分支的最新状态。 - 生成新提交:Git 依照保存的提交序列,将
C4
重新应用到新基础C3
上。通过模拟C4
在C3
基础上的变更效果,Git 创建一个新的提交对象C4'
。由于C4'
的父提交是C3
(而原C4
的父提交是C2
),尽管功能效果与C4
相同,但它是一个全新的提交,拥有不同的哈希值。 - 过程图:
-
-
变基后:
编辑
-
回到
master
分支,并进行一次快进合并后:
编辑
-
4. 更复杂的变基应用——对两个分支进行变基(git rebase --onto <newbase> <upstream> [branch]
)
git rebase --onto <newbase> <upstream> [branch]
命令格式:
-
<newbase>
:新的基底
-
-
- 意义: 指定你希望“重放的提交”应用到的目标分支或提交。
- 作用: 这些提交会被重放到
<newbase>
指定的位置。 - 类型:
-
-
-
-
- 可以是分支名(如
master
)。 - 可以是提交哈希(如
abc123
)。 - 可以是一个引用(如
HEAD~2
)。
- 可以是分支名(如
-
-
-
<upstream>
:基准分支
-
-
- 意义: 确定从哪里开始选取要重放的提交。
- 作用: Git 会找到
<upstream>
和[branch]
的共同祖先,并选取从这个分叉点到[branch]
之间的提交进行“重放”。 - 类型:
-
-
-
-
- 分支名(如
dev
)。 - 提交哈希。
- 可以省略,默认使用
[branch]
的上游分支(即用git branch --set-upstream-to
设置的分支)。
- 分支名(如
-
-
-
[branch]
:当前分支 :
-
-
- 意义: 指定操作的分支,默认为当前分支。
- 作用: 表示重放哪个分支的提交。
- 类型:
-
-
-
-
- 分支名(如 feature)。
- 如果省略,则默认使用当前所在的分支。
-
-
-
模拟你创建了一个主题分支
server
,为服务端添加了一些功能,提交了C3
和C4
。 然后从C3
上创建了主题分支client
,为客户端添加了一些功能,提交了C8
和C9
。 最后,你回到server
分支,又提交了C10
。
编辑
-
截取主题分支上的另一个主题分支,然后变基到其他分支:
-
-
假设你希望将
client
中的修改合并到主分支并发布,但暂时并不想合并server
中的修改。 -
这时使用
git rebas
命令的--onto
选项,选中在client
分支里但不在server
分支里的修改(即C8
和C9
),将它们在master
分支重放:git rebase --onto master server client
-
翻译一下这个命令: “取出
client
分支,找出它从server
分支分歧之后的补丁, 然后把这些补丁在master
分支上重放一遍,让client
看起来像直接基于master
修改一样”。这理解起来有一点复杂,不过效果非常酷。 -
编辑
-
快进合并
master
,使之包含来自client
分支的修改
-
-
-
-
git checkout master
+git merge client
-
快进合并后:
编辑
-
-
- 将
server
中的修改变基到master
上——$ git rebase master server
-
-
git rebase <basebranch> <topicbranch>
简化了先切换到server
分支,再对其执行变基。 -
server
变基后:
编辑
-
- 最终的历史提交并删除
client
和server
分支
-
-
快进合并主分支
master
:git checkout master
+git merge server
-
删除
client
和server
分支:git branch -d client
+git branch -d server
。 -
最终的提交历史:
编辑
-
5. 变基的风险
奇妙的变基也并非完美无缺,要用它得遵守一条准则:
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。(
- 如果你的提交已经被其他开发者获取并使用,避免用
rebase
改变历史。 - 使用
rebase
的原则是: 只能在自己控制的范围内操作 ,避免影响其他人。)
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase
命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
5.1. 变基问题示例
让我们来看一个在公开的仓库上执行变基操作所带来的问题。 假设你从一个中央服务器克隆然后在它的基础上进行了一些开发。 你的提交历史如图所示:
编辑
Figure 44. 克隆一个仓库,然后在它的基础上进行了一些开发
然后,某人又向中央服务器提交了一些修改,其中还包括一次合并。 你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:
编辑
Figure 45. 抓取别人的提交,合并到自己的开发分支
接下来,这个人又决定把合并操作回滚,改用变基;继而又用 git push --force
命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交。
编辑
Figure 46. 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交
结果就是你们两人的处境都十分尴尬。 如果你执行 git pull
命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:
编辑
Figure 47. 你将相同的内容又合并了一次,生成了一个新的提交
此时如果你执行 git log
命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到 C4
和 C6
,因为之前就是他把这两个提交通过变基丢弃的。
5.2. 用变基解决变基
如果你 真的 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch-id”。
如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。
举个例子,如果遇到前面提到的 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交 那种情境,如果我们不是执行合并,而是执行 git rebase teamone/master
, Git 将会:
- 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)
- 检查其中哪些提交不是合并操作的结果(C2,C3,C4)
- 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4')
- 把查到的这些提交应用在
teamone/master
上面
从而我们将得到与 你将相同的内容又合并了一次,生成了一个新的提交 中不同的结果,如图 在一个被变基然后强制推送的分支上再次执行变基 所示。
编辑
Figure 48. 在一个被变基然后强制推送的分支上再次执行变基
要想上述方案有效,还需要对方在变基时确保 C4'
和 C4
是几乎一样的。 否则变基操作将无法识别,并新建另一个类似 C4
的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。
在本例中另一种简单的方法是使用 git pull --rebase
命令而不是直接 git pull
。 又或者你可以自己手动完成这个过程,先 git fetch
,再 git rebase teamone/master
。
如果你习惯使用 git pull
,同时又希望默认使用选项 --rebase
,你可以执行这条语句 git config --global pull.rebase true
来更改 pull.rebase
的默认配置。
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase
命令,这样尽管不能避免伤痛,但能有所缓解。
6. ⭐变基总结
- 什么是变基:
-
- 将一系列提交“重新播放”到另一个分支上,改变提交历史,使其更线性。
- 基本操作:
-
git rebase <目标分支>
:将当前分支的修改基于目标分支重新应用。git rebase --onto <新基底> <旧基底> <分支>
:将特定提交从旧基底移动到新基底。
- 优点:
-
- 提交历史更加整洁、直线化。
- 避免了不必要的合并提交,便于代码审查。
- 风险:
-
- 如果变基已推送的提交,可能破坏其他开发者的工作(引发冲突和混乱)。
- 使用建议:
-
- 可以使用: 对未推送的本地分支清理历史,或整理提交后再推送。
- 避免使用: 对已共享的公共分支执行变基。
- 常用配置:
-
- 默认拉取使用变基:
git config --global pull.rebase true
- 默认拉取使用变基:
- 注意事项:
-
- 若必须在公共分支变基,请通知团队使用
git pull --rebase
。 - rebase 是一种改写历史的操作,改写的历史如果已经被其他人使用,会造成混乱。
- 若必须在公共分支变基,请通知团队使用