Git rebase和cherry-pick
认识 rebase
rebase,正如字面意思,重新选择基点
比如下面这个图,这张图片里有两个分支 feature
和 master
起初,两个分支均为 commit A
为基点,feature
分支延伸出了 D
和 E
两个 commit,master
分支延伸出了B
和C
两个 commit
如果执行了在master
分支执行
1 | # at branch feature |
则feature
分支的基点会从A
变成 master
的最新 commit C
。这就是 rebase
rebase 之后,不论是 feature
还是 master
,他们都在同一条提交线上
对比 rebase 和 merge
还用上面的例子
有这样的一个提交例子
现在试一下不同的操作对提交树产生的影响
如果使用 rebase
为保证相同的顺序 (ABCDE)
,需要切换到 feature
分支,rebase master
分支
1 | # at branch feature |
如果使用 merge
1 | # at branch master |
使用 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
这次提交,把 C
和 D
压缩成一个 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 过程为例,D
和E
在 rebase 到 C
上面的时候,如果 D
和 E
对比 BC
均有冲突,那么应该解决 D
和 E
造成的两次冲突,rebase 的分支有几个 commit 就可能最多产生多少次的冲突
如果一条分支不需要 commit 的每个细节,则可以利用 rebase -i
压缩(squash
) 掉 commit
解决冲突的方式也和 merge 的重新提交不同,应该先把冲突的解决结果使用 add
命令提交到暂存区,然后使用 continue 解决下一个 rebase 的冲突。如此循环,直到没有冲突为准
1 | git add . |
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 是非常好的管理方式。