问题

下面是一个简单的不干净历史:

1
2
3
4
5
6
* D master
|\
| * C origin/master
* | B
|/
* A

当本地和远程各有一次提交后,本地需要使用 git pull 拉取后才能推送,产生了 D 合并提交。

当项目协作的人多了起来后,所有人在同一个主分支开发,而每个人都提交后再拉取,就会带来大堆如此无用的合并提交。几乎每次提交都会有,而每个人一天可以提交数十次,在一个 20000+ 个提交的仓库中,这种无用的合并提交大概会有 10000+ 个,也就是说大概占比 50%,很可怕的数字。

解决方案

要解决无用的合并提交,最好的办法就是不产生它。

使用变基

只有少量提交时使用变基。

大部分情况下的提交修改内容并不会受他人影响,所以可以使用 git pull --rebase 进行拉取,此操作会重新生成已有的提交,会导致提交时间修改,但是作者时间保持不变。

此操作完成后提交历史:

1
2
3
4
5
* B'
|
* C origin/master
|
* A

使用分支

开发特性功能时可以使用分支。

特性功能分支中会有很多提交,这时合并提交是有意义的,可以进行合并。

在 Git 中,每一个提交都有父提交,而且父提交可以有多个。合并提交就是父提交有 2 个以上的提交。

以图的形式显示历史记录时,是按照提交中父提交的顺序显示的。如果我们需要有一条主线,那么需要保证第一个父提交一直是当前的分支所在的提交。

git merge 命令默认情况下会使用 fast-forward 方式合并,即检测到当前分支是要合并入分支的上游时,直接将当前分支指针移动到要合并入的分支完成合并。这种情况下并不会产生合并提交,因此在此情况下会丢失要合并入的分支合入当前分支的记录。如果想要禁用 fast-forward 行为,应该使用 git merge --no-ff <branch> 命令合并。

注意:新建分支时,建议分支名字使用 user/feature 命名方式,这样有助于区分分支的管理者。同时 SourceTree 等 Git 的图形化界面工具会将其显示成目录形式,有助于管理。

自动检查

由于 git pull 命令使用方便,非常容易产生无用的合并提交,所以需要在 Git 服务器上检查。

Git 支持钩子功能,可以在服务器上设置检测无用的合并提交。

注意之前提到的无用提交的图,可以看到,无用提交的第一个父提交不是 origin/master,而是用户本地的提交。简单说,服务器可以检查合并提交的第一个父提交是不是当前分支即可。

推荐使用现成的 Git 检查钩子:networm/git-check-useless-merge: Deny push if first parent of last merge commit is not remote branch.

参考资料