认识 rebase

rebase,正如字面意思,重新选择基点

比如下面这个图,这张图片里有两个分支 featuremaster

起初,两个分支均为 commit A 为基点,feature 分支延伸出了 DE 两个 commit,master分支延伸出了BC两个 commit

如果执行了在master分支执行

1
2
# at branch feature
git rebase master

feature分支的基点会从A变成 master的最新 commit C。这就是 rebase

rebase 之后,不论是 feature 还是 master ,他们都在同一条提交线上

对比 rebase 和 merge

还用上面的例子

有这样的一个提交例子

现在试一下不同的操作对提交树产生的影响

如果使用 rebase

为保证相同的顺序 (ABCDE),需要切换到 feature 分支,rebase master 分支

1
2
# at branch feature
git rebase master

如果使用 merge

1
2
# at branch master
git merge feature

使用 merge 依然是两条线,使用 rebase 则是一条线

使用 merge 还多出了一个 commit

使用 rebase 整理提交历史

除了用来合并代码,rebase 还可以用来整理提交历史

触发交互式 rebase:--interactive -i

继续上面的例子,选定 commit A 为基点,执行命令

1
git rebase -i `commit A`

执行命令后会进入一个编辑界面

pick(或 p):保留该提交。
reword(或 r):保留提交但修改提交消息。
edit(或 e):暂停 rebase 并允许你修改该提交,比如修改内容、添加文件等。
squash(或 s):将该提交与前一个提交合并。你可以修改合并后的提交信息。
fixup(或 f):与 squash 类似,但丢弃当前提交的提交信息,直接合并到前一个提交。
exec(或 x):在当前提交点执行一个 shell 命令。
drop(或 d):删除该提交。

假设希望删除 commit E 这次提交,把 CD 压缩成一个 commit,并且修改 commit 消息为 CD,可以这样做

保存之后,会提示修改 commit 消息

之后,提交历史会变成这个样子

cherry-pick

cherry-pick,摘樱桃。很形象地表示这个命令是要从提交树上删除掉一些 commit,和 git rebase -i 中的 drop 操作类似

这个命令经常配合 rebase 使用,比如某次提交的代码有问题,可以先备份一下,然后从主分支 cherry-pick 掉

cherry-pick 多个 commit 的时候,需要按照与原始顺序相反的顺序来删除

如果 commit1 依赖于 commit2,那么应该是

1
git cherry-pick <commit1> <commit2>

这样有利于减少冲突,避免依赖性问题

冲突解决

rebase 和 cherry-pick 也会产生冲突,并且解决步骤和 merge 不同

merge 是将一条分支上的所有代码汇入另一条分支,其操作的维度是分支,所以只会有一次冲突

而 rebase 不同,rebase 的操作维度是 commit,以上面 ABCDE 的 rebase 过程为例,DE 在 rebase 到 C 上面的时候,如果 DE 对比 BC 均有冲突,那么应该解决 DE 造成的两次冲突,rebase 的分支有几个 commit 就可能最多产生多少次的冲突

如果一条分支不需要 commit 的每个细节,则可以利用 rebase -i 压缩(squash) 掉 commit

解决冲突的方式也和 merge 的重新提交不同,应该先把冲突的解决结果使用 add 命令提交到暂存区,然后使用 continue 解决下一个 rebase 的冲突。如此循环,直到没有冲突为准

1
2
git add .
git rebase --continue

rebase 的优点和缺点

rebase 的优点

  • rebase 保持了提交历史的线性和简洁,方便管理
  • 有效减少了合并提交,让每个 commit 都有意义

rebase 的缺点

  • 操作不当会丢代码,如果没有分支备份,在解决冲突的时候,以及使用 drop 和 cherry-pick 的时候,会造成代码的丢失
  • 解决冲突的过程比较复杂
  • 依赖历史 commit 的线性结构
  • 开发过程中断的情况,如果某个分支被搁置了很久,这个分支再去 rebase 最新代码的时候,可能会产生巨量的冲突需要解决

分支管理和使用经历

在米哈游的平台组工作时,前端团队用的就是 rebase 的管理方式。

维护协作分支 develop(ci 对应 test 环境),pre 和 master 分支(ci 对应 pre 环境),tag 为线上环境发布。

在进行需求开发的时候,每个人会从 develop 分支拉出自己的开发分支,在提测的时候,会将自己的开发分支 rebase 一下 develop,通过 git lab 提交 merge request 进入 develop 测试环境。

团队大约十几个人,同时开发多个模块,使用 merge 的话会让提交树变得非常复杂,难以从中间摘掉某个提交,或者快速定位和解决问题。rebase 是非常好的管理方式。