Github Pull Request 三种合并方式

Page content

前言

最近呢在github上开源了一个golang实现的socks5协议的仓库(PS:如果觉得不错点个 star呢,谢谢)。其中关于pull request(下文就简称pr)合并这一块,准备使用git rebase 来得到一条没有分叉的提交历史。

rebase合并冲突的问题

起初,正常使用github rebase and merge来合并pr,并没有注意到异常情况。但后来随着其他开发者的加入,合并的pr越来越多;突然在一次pr合并时发现了其中有已经合入过的commits,并且rebase 提示有冲突。

然后就发现了github rebase 与 ·git rebase·的 不同之处。github rebase 会把pr中的每个提交的提交者都修改为 author + reviewer ,且重新生成commit id

例,请注意pr中的commit id,和rebase 合并后的 commit id

合并前:

合并后:

使用 github rebase and merge 会修改 pr中commits的作者信息和commit id。

造成提交历史混乱

在这个pr合并后,如果这个开发者不基于最新的上游分支(upstream)分叉新创建分支,而是继续且长期使用本地分支;如果要更新当前分支,那么这个开发者必须使用 git rebase 来变基到upstream分支上。

为什么不使用git merge呢?如果使用git merge命令就会出现重复的提交历史,因为upstream 分支上是commit id是已经变化的提交(虽然提交的内容是一模一样,但还是会被人为是不同的两次提交)。

使用git merge,如下:

可以看到重复的提交。假如此开发者提交一次后,在推送到远端,并创建pr;该pr中原本应该只有一次提交,但是却多出来2次重复的提交。这就导致了历史的混乱。如下,红框中为重复的提交:

而使用git rebasegit 可以检测到重复的提交,即你本地的提交与远端github rebase后的提交是一回事(只是github rebase 更改了你的commit id),故在本地使用git rebase可以直接将本地分支更新为upstream上最新的(本地的commit id等信息被upstream 上的直接覆盖)。

此例中,虽然pr中显示有4个提交,但是实际有影响的只有最新的那一个提交(重复的提交只会影响历史,并不影响代码内容,因为重复提交的更改已经合并过了),但是将重复的提交历史合入仓库中,势必会影响代码仓库的历史追溯(可以使用squash and merge 来压缩这一次pr)。但是,开发者打开pr查看历史会感到迷惑,所以应该要避免这种情况。

github上其余两种合并方式

merge pull request

commit id 等都不变,且会多生成一个merge pull request的提交。

squash and merge

将一个pr中的所有commit压缩为一个commit再合并。假如原本pr中有5次提交,压缩并合并后只会有一个特定格式的commit。

格式如下:

提交数 摘要 描述
一个提交 单个提交的提交消息标题,后接拉取请求编号 单个提交的提交消息正文
多个提交 拉取请求标题,后接拉取请求编号 按日期顺序列出所有被压缩提交的提交消息

结论

一定要特别注意

  • 作为代码审查者一定要认真审视pr所包含的内容及带来的影响。

  • 作为开发者,最好不要长期使用一个分支;因为在更新本地分支时,不论你用merge or rebase 都会带来新的重复的提交(以前的提交都被压缩为一个新的提交),如果继续用该分支新建pr的话极有可能引入重复的提交。

  • 在一次pr合并后就删除来源分支。