使用 git rebase 提升 PR 质量

在 Github 上以提交 PR 的方式参与开源项目是十分简单的。不过因为 Git 自己自由度较高,有些随意提出的 PR 其实是会影响项目历史记录的【脏】PR。下文介绍什么时候会发生这种状况,以及如何经过 rebase 工做流改进它。git

什么是脏 PR

咱们知道,若是你想为某个开源项目贡献代码,通用的流程是:shell

  1. fork 项目到本身的仓库。
  2. 在新开的分支上提交。
  3. 提出 PR 请求维护者将你的新分支合并至原项目。

在最后一步中,你所提交的 PR 会包括新分支上所有的历史记录。这时候,若是出现下面的几种状况之一,在这里咱们就认为这个 PR 属于【脏】PR:vim

  1. PR 分支和原仓库的目标分支存在冲突
  2. PR 包含了许多琐碎的 commit 记录,如 fix bug / add dev 等缺少实际意义的提交信息。
  3. PR 包含了多个没必要要的 Merge 记录。通常来讲,fork 出的仓库和原仓库保持同步的最简单方式,是 fetch 原仓库后将 HEAD merge 到当前分支。这个操做每执行一次,就会在当前分支留下一个相似 Merge xxx 的 commit 记录。
  4. PR 包含了与主题不符的改动,如留下冗余的日志文件、在其它模块中添加了额外的调试用代码等。

如何处理脏 PR

内部项目

上述的几种状况,在开发托管在 Stash 或 Gitlab 上的内部项目时,其实都不是问题,都有着很是简单的解决方案:安全

  1. 冲突了?直接拉主分支拉下来改啊,反正你们都是管理员✌️
  2. commit 怎么写有问题吗?原本不就是天天下班准时 commit 一次吗?
  3. 看看咱们的 git log --graph,多么壮观!你们都很努力的好吗!
  4. 能按时提测就行,不要在乎这些细节🙄。

并非说这么处理有什么问题,尤为在中国特点每天赶进度的业务项目中,这么作也基本上是最佳实践了。下面,咱们重点讨论的是在较为正式地向外提交 PR 时,提高 PR 质量的方法。bash

merge --squash

Github 在很早以前就支持了强制 squash 功能。经过这种方式,原仓库的维护者能够在将 PR 提交的分支所更改的内容,squash 到主仓库的一次提交中。这样,无论提出 PR 的分支有多【脏】,均可以在并入时获得净化了。这大体至关于命令行下这样的操做:fetch

git merge forked_lib/new_branch --squash
git commmit -m 'something from new_branch'复制代码

这是获得 Github 官方支持的实践,但这么处理有什么局限性呢?主要是这两点:this

  1. 须要原仓库维护者解决冲突并整理历史,而不是 PR 提出者。
  2. 只能将多个 commit 整理为一个,而不是若干个。

这个方式最棘手的问题实际上在于:它把编辑提交历史的责任丢给了原仓库的维护者,PR 提交者并不能在提交 PR 前清理历史记录。是否有更好的方案呢?spa

rebase

经过 git rebase 命令,咱们可以得到对 git 提交历史更大的掌控。不过,这也是一个存在风险的命令,所以在实际使用前建议稍加了解其原理。命令行

首先假设项目主干分支是 master,你在 fork 而来的仓库下新增了 dev 分支。你从 master 的 m1 提交开始,在 dev 提交了 d一、d2 和 d3 三次提交。这时,master 也更新了 m2 和 m3 两次提交。这时候版本树大体长这样:调试

m0 -- m1 -- m2 -- m3
      |
      d1 -- d2 -- d3复制代码

这时你的目标是将三次 dev 上的 commit 合并为一个新的 d,让 dev 的历史变成这样:

m0 -- m1 -- m2 -- m3 -- d复制代码

为了实现这一点,你能够在 dev 上 rebase 到 master:

git checkout dev
git rebase -i master复制代码

rebase 的原理是:

  1. 首先找到两个分支(dev 和 master)的最近共同祖先 m1。
  2. 对比当前 dev 分支相比 m1 的历次提交,提取修改,保存为临时文件。
  3. 将分支指向 master 最新的 m3。
  4. 依次应用修改。

在【依次应用修改】的这一步中,你能够进一步选择如何对待 d一、d2 和 d3 的 commit message。在以 -i 参数启动了交互式的 rebase 后,会进入 vim 界面,由你选择如何操做 dev 上的提交记录,形如这样:

pick 91398f93 d1
pick 65efc762 d2
pick b82e050d d3

# Rebase 4652f96d..b82e050d onto 4652f96d (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out复制代码

你能够编辑对 dev 上这几个 commit 的处理,如输入 pick 为保留,输入 squash 则将该 commit 内容并入上一个 commit 等。在完成操做选择后(这里咱们能够选择 fixup d1 和 d2,并 reword d3),输入 :wq 保存退出,会进入一个新的 vim 窗口,在此你能够进一步编辑新的 commit message,保存后 rebase 便可生效。注意,你至少须要选择一个须要 use 的 commit,不然会报错。

rebase 生效后再查阅分支历史记录,是否是清净多了呢?在这个状态下提交更清爽的 PR 吧😉

在此额外提醒一点,对于已经被 fork 出多份的仓库,rebase 原仓库的主干是危险操做。除此以外,使用 rebase 修改私有分支的历史记录是很安全的。

回头看看脏 PR 的几个问题,如何经过 rebase 解决呢?

  1. 遇到和远程主库的冲突时,能够先将远程仓库 fetch 下来,然后将本身的 dev 分支 rebase 到新的 HEAD 上。
  2. 冗余的 commit 记录能够直接 rebase 合并。
  3. 和 1 相似地,经过将本身的 dev 分支 rebase 到新的远程库 HEAD 的方式,不会留下冗余的 Merge 记录。
  4. 提交一个新 commit 修复问题,然后 rebase 便可。

到此,对 rebase 的介绍大致上就结束了。但愿对你们更好地参与开源项目有所帮助。

参考:

相关文章
相关标签/搜索