使用 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 的介紹大致上就結束了。但願對你們更好地參與開源項目有所幫助。

參考:

相關文章
相關標籤/搜索