1. 程式人生 > 實用技巧 >【noip2019提高組】Emiya 家今天的飯

【noip2019提高組】Emiya 家今天的飯

此文轉載自:https://my.oschina.net/zoker/blog/4733364
大咖揭祕Java人都栽在了哪?點選免費領取《大廠面試清單》,攻克面試難關~>>>

說起 PullRequest 相信大部分人都不會陌生,它是由 Github 推出的一種開源協作模式,由於 Gitlab 佔據著企業內部私有部署的半壁江山,這種模式也更多的用在企業內部程式碼稽核流程,也就是所謂的 CodeReview。其實還有很多企業和團隊會選擇 Gerrit 這個工具,Gerrit 提供的是 ChangeRequest 模式,這種模式更具有針對性,對程式碼稽核的粒度也更細,近期有客戶需求在 Gitee 上實現類似 ChangeRequest 的需求,所以針對兩種模式做一個介紹,探討兩種模式的具體適用場景。

什麼是 CodeReview

關於 CodeReview 是什麼,Gerrit 官方文件給了一個非常形象的解釋,一個軟體或者 Feature 的生產過程,如同一首歌的問世前一樣,需要歌手反覆錄製不同的片段達到最佳的效果,要保證每一個片段都令人滿意,並且要把所有的片段融合起來,最終目的就是把最好的版本提供給大家品鑑。Queen 樂隊有一首《波西米亞狂想曲》,用了3周進行錄製,其中有一些片段甚至重複錄製了180多次,最終才有了這首經典。

同樣的情況也在軟體工程領域每天上演著,開發者們根據同事的反饋,一遍又一遍的修改、優化自己的程式碼,最終產出一個高質量版本的產品交付到使用者手中,這就是 CodeReview(程式碼評審)所做的事情。

一件理論或者實踐的推動,總是要有相應的價值產生。那麼 CodeReview 能夠給我們帶來什麼呢,來看看 Gerrit 的總結:

  1. Code reviews mean that every change effectively has shared authorship
  2. Developers share knowledge in two directions: Reviewers learn from the patch author how the new code they will have to maintain works, and the patch author learns from reviewers about best practices used in the project.
  3. Code review encourages more people to read the code contained in a given change. As a result, there are more opportunities to find bugs and suggest improvements.
  4. The more people who read the code, the more bugs can be identified. Since code review occurs before code is submitted, bugs are squashed during the earliest stage of the software development lifecycle.
  5. The review process provides a mechanism to enforce team and company policies. For example, all tests shall pass on all platforms or at least two people shall sign off on code in production.

> 摘自: https://gerrit-documentation.storage.googleapis.com/Documentation/3.2.3/intro-rockstar.html#review

簡單翻譯一下:

  1. 讓每一個提交(變更、改動)都有多個 Owner(好處就是出了問題不會有單點故障,任何一個 Owner 都可以處理)
  2. 雙向的知識傳遞:評審人可以從提交者那裡看到新的程式碼如何運作的,提交者可以從評審者那裡學會專案中的一些最佳實踐
  3. 程式碼評審能夠提供一個正向的機制,讓更多的人蔘與到評審中來,那麼就會有更多的 Bug 或者提升被發現
  4. 越多的人評審了程式碼,Bug 被發現的機率越大,Bug 可以在整個軟體開發生命週期的前期就被處理掉
  5. 這種評審可以助力團隊及公司制度的完善,比如可以規定投產的程式碼必須有兩個人以上的簽名才可以

PullRequest & ChangeRequest 簡介

PullRequest 最初是由 Github 推出的一種開源協作模式,目前 Gitee、Gitlab等平臺都支援這種協作模式,而且這種模式目前更多的用在了團隊或者企業的內部程式碼評審層面,這種協作模式一般是由開發者 Fork 一個倉庫,或者在倉庫內新起一個分支如zoker/feature-1,無論是哪種方式,所有的大前提是:開發者沒有目標分支的寫許可權,寫許可權是被嚴格控制的,必須經過 PullRequest 方式合入程式碼。在相應的分支研發並自測完成後,通過 Web 介面 提交一個 PullRequest 請求將這些程式碼合入到目標分支,這個過程會有自動化測試、自動編譯構建、程式碼評審、程式碼改進、程式碼合併等一系列操作,最終完成程式碼向目標分支的合入。

ChangeRequest 是由 Gerrit 推出的一個概念,Gerrit 是為 AOSP(Android Open Source Project)寫的,結合 Repo 工具用來管理龐大的安卓專案,多倉管理也是他的優勢之一,但是更多的人把它視為程式碼評審神器,能夠使每一個 Commit 都是可靠。這種模式同樣需要嚴格管控目標分支的寫許可權,開發者可以在本地起一個分支進行開發,與 PullRequest 不同的是,ChangeRequest 不需要開發者到 Web 上進行評審請求的提交,推送一個 Commit 到對應的分支之後,會自動建立一個評審單。而且另外一個比較大的區別就是,ChangeRequest 的評審是針對單 Commit 評審,而 PullRequest 針對的是一個或者多個 Commit 的集中評審。

下面就以 Gitee 和 Gerrit 為例,從幾個方面簡單介紹下兩者在日常研發協作上的區別

Diff_1:使用

從使用上來講,PullRequest 以及 ChangeRequest 的最大的差別在以下幾點:

1. 評審的建立

PullRequest 需要我們的研發人員在做完一項研發工作後,在推送相應的特性分支到 Gitee 後,需要在 Gitee 上進行評審單的建立;而 ChangeRequest 則不需要,因為在推送後,Gerrit 會自動建立一個評審,也就是一個 ChangeRequest。

PullRequest 由於是開發者直接提交的,所以可以在提交 PullRequest 的時候就寫好各種描述資訊;而 ChangeRequest 仍需要前往 Gerrit 的 Web 介面進行重新完善

Gitee 建立 PullRequest 介面

2. 評審的內容

PullRequest 一般都是包含一個以上的 Commit 的評審,是一個 Commit 集合。當開發者接受到修改意見後,一般都是直接基於當前的提交繼續進行提交,來處理程式碼的改進,Gitee 會自動更新 PullRequest 來更新這些改動,在一個 PullRequest 的生命週期中,可能會產生多個提交。因為在最終可交付的時候,整個 PullRequest 可能存在很多提交,這些提交可能是程式碼改進,也可能是衝突的合併,不過在合併的時候可以有多種形式的 Merge 方式,可以選擇是將歷史合入還是重新整合為一個 Commit 進行提交。

Gitee 多種合併方式的選擇:

而對於 ChangeRequest 來說,則是針對於單 Commit 的評審,每個 Change 都只有一個提交。對程式碼的改進一般都是修改當前 Commit,也就是git commit --amend,Gerrit 接受到新的提交後,會自動更新這個 Change,並且會保留之前的提交,作為本 Change 的一個 Patchset。同樣,在一個 ChangeRequest 生命週期中,可能會有多個 Patchset 產生,不過在最終合併的時候合入的是經過多次修改後最優質的那個 Patchset。

Gerrit 的一個 Change 包含多個 Patchset:

那肯定會有人有疑問,我每次推送到同一個 ChangeRequest 的 Commit,由於修改了內容或者描述,CommitID 早就變了,Gerrit 如何辨別區分是同一個修改?這就是 Gerrit 設計上優秀的地方,雖然 CommitID 變了,但是可以通過 Git 的鉤子插入一個 ChangeID,這個 ChangeID 就是識別同一個 Commit 的重要武器,詳細可以參閱官方文件,這裡不再贅述。

以上兩個是在使用上最主要的差異,Gitee 提供的 PullRequest 功能對功能需求拆分度要求不高,因為我們可以在同一個 PullRequest 裡面做很多事(這是不推薦的),但是對於 Gerrit 的 ChangeRequest 來說,需要對需求拆分的相對獨立,需求粒度一定要小,相互之間不可以相互影響,否則對於按照 Commit 進行評審的模式來說,各種衝突依賴就亂套了。

Diff_2:管理

從配置管理員的角度來看,Gitee 和 Gerrit 的配置管理差別還是比較大的,尤其是許可權管理。

Gitee 對於許可權的管理主要是從開發人員角色、分支許可權等角度進行配置,相對來說比較簡單,配置項清晰明瞭,可以通過簡單的配置即可實現 PullRequest 協作流程的基礎功能。從使用成本上來講是非常高效的,各個許可權隔離也比較清晰,就算是一個入門的配置管理員也能夠快速理解並完成配置。

Gerrit 的配置相對來說就比較複雜了,但是更加靈活,各個配置項均可靈活配置,但是靈活帶來的就是使用成本的提升,使用成本相對 Gitee 比較高。不過好在提供了全域性的繼承功能,能夠在已經有配置模版的情況下快速覆蓋,Gerrit 中可以自建使用者組,並且可以對不同專案的不同使用者組進行靈活的許可權配置。壞處就是如果一不小心動到一個並不瞭解的配置項,可能就會造成程式碼許可權的洩漏,所以對配置管理員的要求更高。

Diff_3:多倉管理

在一些大型的工程如安卓專案、鴻蒙OS等,將所有的程式碼放到一個程式碼庫顯然是不可能的事情,一方面是體積過大,無法高效管理,嚴重降低開發者的效率;另外一個重要的方面是許可權管理,每個開發者都擁有一整個系統的程式碼顯然是不合適的。

那麼,該如何解決這種問題呢,Git 給出了他自己的兩種解決方案:

  • Git Submodule
  • Git Subtree

Git Submodule 是目前用的比較多的一種解決方案,主要的功能就是將其他倉庫的某個版本作為當前倉庫的一個目錄,掛在當前倉庫下,所有的配置資訊都存放在當前倉庫的.gitmodules檔案下,包含了子倉庫的資訊以及對映的目錄

[submodule "lib"]
	path = lib
	url = https://gitee.com/xxxxx/lib.git

這樣做的好處是lib目錄可以單獨進行許可權的管理,不至於許可權過於發散。Git Subtree 的實現類似,它是 Git 1.5.2 之後官方新增的一個功能,旨在替代 Git Submodule 管理共用倉庫,兩種方式都可以達到多倉管理的功能,只不過 Submodule 是引用,而 Subtree 是拷貝,具體的使用可以參見 Git - - subtree與submodule。Submodule 以及 Subtree 在主流的程式碼託管平臺都支援,Gitee 也一樣,畢竟這是 Git 的 Native Feature。

那麼,為什麼已經有了 Submodule 了,谷歌團隊還要搞一個 Gerrit 出來呢?主要還是管理上的不方便,開發上的效率低下,所有才有了 Gerrit + Repo 這套工具。

如上所述,Repo 工具是谷歌開發的用於管理 Android 版本庫的一個工具。Repo 並不是用來取代 Git的,它是使用 Python 對 Git 的一層封裝,簡化了對多個 Git 版本庫的管理方式。Repo 主要是結合著 Gerrit 來使用,它以一個manifest.yml為中心,通過對專案結構化的描述,達到與 Submodule 同樣的效果,但是比 Submodule 更加靈活和方便。

<!--?xml version="1.0" encoding="UTF-8"?-->
<manifest>
  <remote name="gitee" fetch="[email protected]:{namespace}" autodotgit="true" /> <!--fetch=".." 代表使用 repo init -u 指定的相對路徑 也可用完整路徑,example:https://gitee.com/MarineJ/manifest_example/blob/master/default.xml-->
  <default revision="master" remote="gitee" /><!--revision為預設的拉取分支,後續提pr也以revision為預設目標分支-->

  <project path="lib" name="lib" />  <[email protected]:{namespace}/{name}.git  name項與clone的url相關-->
  <project path="src" name="src" /> 

</manifest>

為什麼說 Repo 主要是結合著 Gerrit 來使用呢?因為 Gerrit 的特性就是推送新的提交可以自動建立評審,那麼在一個大型工程的研發流程下,一個功能經常性的需要跨倉庫進行開發,開發完成後需要提交評審單進行評審。如果一個功能涉及到了三個倉庫,那麼在進行了對應的開發後,只需要執行 Repo 提供的相關命令,即可在 Gerrit 上產生對映在三個倉庫的三個 ChangeRequest,並且還可以在 Repo 命令追加評審人、話題等一系列屬性。

當然,Repo 工具如果作為批量倉庫提交工具,也是可以在 Gitee 上使用的,但是推送完成之後呢?需要開發者手動建立三個 PullRequest!這無疑帶來了非常大的開發成本,開發者也會因此而抱怨。所以 Gerrit + Repo 這套工具其實就是為了解決大型工程的專案管理及程式碼評審而設計的。

不過,在今年年中的時候,我們 Gitee 團隊對 Repo 工具進行了 Fork Flow 的支援,讓 Repo 工具可以在推送之後,自動在相關的倉庫裡面建立 PR,免去了複雜的建立流程,相關使用和實現可以參見 oschina/repo

Diff_4:持續構建

前面我們介紹了可以使用 Submodule 或者 Submodule 來統一管理多倉的場景,在這種模式下,持續構建變得也容易了,畢竟所有的程式碼都是主倉庫擁有的。這裡以 Jenkins 為例,我們只需要配置 Gitee-Jenkins-Plugin 外掛即可, Gitee-Jenkins-Plugin 是 Gitee 團隊基於 GitLab Plugin 二開的一個 Jenkins 外掛,包含但不限於以下功能:

  • 推送程式碼到 Gitee 時,由配置的 WebHook 觸發 Jenkins 任務構建
  • 提交 Pull Request 到碼雲專案時,由配置的 WebHook 觸發 Jenkins 任務構建,支援PR動作:新建,更新,接受,關閉,審查通過,測試通過
  • 按分支名過濾觸發器。
  • 構建後操作可配置 PR 觸發的構建結果評論到碼雲對應的PR中。
  • 構建後操作可配置 PR 觸發的構建成功後可自動合併對應PR。
  • ... 更多的可以參見專案的 Readme

可是,如果使用 Repo 工具在 Gitee 進行多倉管理呢?所有的倉庫都是獨立的,我們該如何知道哪些倉庫變更了呢?在 oschina/repo 的實現中,我們也遇到了這個問題,當時與客戶溝通提了這麼個解決方案:使用者在提交程式碼後,Repo 工具會自動建立 PullRequest,與此同時,開發者將倉庫以及對應的 PullRequest 資訊提交到manifest倉庫的一個 Issue,通過 Issue 的 Webhooks 觸發流水線,流水線獲取資訊後進行對應的編譯構建操作。這麼做雖然可以解決問題,但是太不優雅了,對開發者非常不友好。

那麼,Gerrit 如何處理這種情況呢?

在 Gerrit 的一個 Change 中,可以建立 Topic 將多個 Change 關聯起來,這樣就可以使用 Jenkins 上面的兩個外掛: Jenkins RepoGerrit Trigger

  • Jenkins Repo: This plugin adds Repo as an SCM provider in Jenkins.
  • Gerrit Trigger: This plugin integrates Jenkins to Gerrit code review for triggering builds when a "patch set" is created.

可以利用 Topic 這個特性,結合這兩個外掛,通過配置,在有新的 Patchset 提交的時候,通過 Topic 的關聯,來針對性的拉取有更新的 Change 進行編譯構建。這裡需要注意的是,Repo 工具每次的提交可能是多個倉庫同時進行推送的,因為開發者在本地的行為是改動多個依賴倉,本地驗證通過後一併提交的。

使用 Repo 作為倉庫源的配置:

使用 Gerrit Trigger 進行靈活的配置:

關於融合的一點思考

話說天下大勢,分久必合,合久必分,道理放到哪裡都一樣。使用 Gerrit 就必須重視配置管理,對於一些使用體驗上就沒有那麼便捷,前期的使用培訓成本較高,Review 過程相對隔離,對於一直使用 PullRequest 模式的開發者並不是特別友好。而 Gitee 能夠讓我們快速構建起一個標準的研發協作流程,但在使用 Gitee 提供的各種方便功能,減少我們配置的複雜度的同時,對於多倉管理及評審的場景,Gitee 並不是強項,但是 Gitee 也在進步:

  • 提供了按照 Commit 進行評審的互動,部分客戶還做了 Commit 檢視,能夠召集團隊對所有 Commit 一個一個進行評審並且可以打標記
  • 提供了 Repo 的 Fork Flow 模式

Gitee(其它平臺也一樣)可以做的事情也還有很多:

  1. 提供程式碼稽核不同介面的門禁介面,更加方便與不同的自動化測試及編譯構建對接,用結果變更狀態
  2. Repo Fork Flow 可以改造為自動提交 Issue 變更單,結合流水線達到 Gerrit 的 Topic 的效果
  3. 提供推送分支自動建立評審的功能(其實 Gitee 已經有輕量級PR了,只是沒對本地推送自動建立做支援)
  4. PullRequest 的 Commit 支援類似於 Gerrit 的逐個審查,進一步協助團隊提升程式碼質量
  5. ...

Gerrit 也有可改進的點:

  1. 整合相關聯的 Commit 到一個 ChangeSet 集合檢視,提供類似於 PullRequest 的評審體驗
  2. 提供更多的組織架構層級顯示的功能,方便管理和整合許可權
  3. 提供統計報表等度量工具,協助團隊管理及團隊內部競爭氛圍的提升
  4. ...

最後,附一張來自 AOSP 的關於 Gerrit 的《Life of a patch》供大家參考學習

(END)