啟用合併佇列
**合併佇列**(也稱為 **提交佇列** 或 **合併列車**)藉由提供兩個主要功能來改善持續整合 (CI) 系統
- 如果 Git 分支是在合併*之後* 而非在合併*之前* 驗證,則可**提高安全性**,避免發生組建中斷
- 藉由智慧地合併工作或平行處理作業來**提高輸送量**
合併佇列可以是熱門 CI 系統(例如 GitHub 或 GitLab)的內建功能,也可以是附加服務。
動機範例
假設提取請求 1 和 2 等待合併到您的 `main` 分支,而它們的分支名稱為 `pr1` 和 `pr2`。傳統上,驗證方式有幾種基本方法
**速度慢但安全:**我們使用 `start` 來表示 `main` 分支的最新提交。CI 系統會建立一個臨時分支 `start+pr1`(將 `start` 與 `pr1` 合併)。我們會建置這個「熱合併」,如果成功,現在就可以將 PR 1 合併到 `main`。如果 PR 2 有正在進行的組建,則應該中止,因為 `main` 已變更。它的熱合併需要使用 `start+pr1+pr2` 重新執行,因為這將是 PR 2 合併後的 `main` 中的內容。這種方法可確保 `main` 中每個提交的正確性。但是,在作用中的單一儲存庫中,積壓工作會很快堆積,因為最終合併的組建根本沒有平行處理。
**樂觀:**比較不嚴格的做法,我們可以選擇允許 PR 2 只使用 `start+pr2` 的成功組建來合併,即使最終提交將會是 `start+pr1+pr2`。實際上,我們希望如果 `start+pr1` 和 `start+pr2` 組建成功,`start+pr1+pr2` 也會如此。這通常是正確的,但舉例來說,如果 PR 1 重新命名一個 API,而 PR 2 引入對該 API 的新呼叫,那麼它們的組合將會失敗,即使它們個別成功也是如此。
樂觀方法明顯更快,因為 PR 1 和 PR 2 可以平行建置並以任何順序合併。但是,每當 `main` 分支中斷時,就會發生不幸的事件,需要還原 PR 或合併修正程式才能回到良好狀態。根據支援人員而定,這可能需要數小時甚至數天,在此期間每個人的工作都會中斷。在高流量單一儲存庫中,這些事件的代價變得過於高昂。
**過於樂觀:**值得一提的是,早期的系統甚至沒有執行熱合併。它們使用樂觀策略,但可能使用了非常過時的 `main` 基準。可能會使用策略來限制基準的舊程度,以小時或 Git 提交來測量。
合併佇列如何提供協助
首先,我們決定安全性是不可協商的:在 PR 1 合併到 `main` 之後,我們不會接受以 `start+pr2` 的成功組建為基礎的 PR 2。為了安全起見,我們堅持要有 `start+pr1+pr2` 的成功組建。
合併佇列的主要洞見是,`start+pr1+pr2` 可以更早開始。以下是假設的時間軸
時間 | PR 1 | PR 2 | `start+pr1` 建置 | `start+pr2` 建置 | `start+pr1+pr2` 建置 |
---|---|---|---|---|---|
1:00 | 已建立 | ||||
1:01 | . | 開始 | |||
2:00 | . | 已建立 | . | ||
2:01 | . | . | . | 開始 | 開始 |
4:00 | . | . | . | . | . |
5:00 | . | . | 成功 | . | . |
5:01 | 已合併 | . | . | . | |
5:02 | . | 已取消 | . | ||
6:00 | . | . | |||
7:00 | . | 成功 | |||
7:01 | 已合併 |
為什麼我們要建置 `start+pr2`,然後又取消它?如果 PR 2 碰巧先完成,則需要該作業,這可能如下所示
時間 | PR 1 | PR 2 | `start+pr1` 建置 | `start+pr2` 建置 | `start+pr1+pr2` 建置 |
---|---|---|---|---|---|
1:00 | 已建立 | ||||
1:01 | . | 開始 | |||
2:00 | . | 已建立 | . | ||
2:01 | . | . | . | 開始 | 開始 |
4:00 | . | . | . | . | . |
5:00 | . | . | . | 成功 | . |
5:01 | . | 已合併 | . | . | |
5:02 | . | 已取消 | . | ||
6:00 | . | . | |||
7:00 | . | 成功 | |||
7:01 | 已合併 |
不應該有 `start+pr2+pr1` 的額外欄位嗎?因為這會是 `main` 中最終的內容?不用,簽出的檔案與 `start+pr1+pr2` 相同。組建驗證只關心原始程式碼的內容,而不是其 Git 歷程記錄。
請注意,隨著作用中 PR 的數量增加,分支組合的數量也會爆炸性增加。例如,如果我們有三個並行 PR,我們可能需要六個作業:`start+pr1`、`start+pr2`、`start+pr3`、`start+pr1+pr2`、`start+pr2+pr3` 和 `start+pr1+pr2+pr3`。建置所有組合可能會快速耗盡我們的機器集區。
為了避免資源成本爆炸性增加,我們可以略過看起來相對不可能的組合,並且仍然從平均的平行處理中獲益。舉一個極端的例子,如果我們對 PR 1、PR 2 和 PR 3 會成功非常有信心,那麼我們可能只需要一個作業 `start+pr1+pr2+pr3`;只有在失敗時才能嘗試其他組合。顯然,複雜的實作有許多機會可以顯著超越更基本的合併佇列。
運用 Rush 工作區相依性
🚧 即將推出:此功能尚未準備就緒。
繼續上述範例,假設 PR 1 是 `project-a` 下的修正,而 PR 2 是 `project-b` 下的修正;也就是說,每個 PR 的 Git 差異只會影響一個專案資料夾下的檔案路徑。此外,假設在 Rush 工作區內,沒有其他專案相依於 `project-a` 或 `project-b`。這表示
- 由 `rush build --from project-a` 建置的原始程式碼,對於分支 `start+pr1` 和 `start+pr1+pr2` 而言是相同的。
- 由 `rush build --from project-b` 建置的原始程式碼,對於分支 `start+pr2` 和 `start+pr1+pr2` 而言是相同的。
這些假設保證 PR 1 和 PR 2 完全獨立。我們可以獨立建置它們,並以任何順序安全地合併它們的分支。合併佇列完全不需要建置 `start+pr1+pr2`。
接下來,假設 `project-b` 的 **package.json** 檔案改為指定相依於 `project-a`。在這種情況下,PR 不再獨立:在 PR 1 合併後,必須先驗證 `start+pr1+pr2`,才能安全地合併 PR 2。
此分析依賴於專案資料夾之間相依性的知識,而這些知識在程式設計語言和組建系統之間差異很大。即使在 JavaScript 生態系統中,**package.json** 檔案的解譯也需要針對 PNPM、Rush+PNPM、Yarn 等進行特殊考量。
合併佇列通常提供描述資料夾相依性的基本功能,可能是可以描述靜態關係的 glob,例如
- 「此資料夾包含 JavaScript 程式碼,而該資料夾包含 Golang 程式碼,因此它們之間不可能有任何相依性。」或
- 「此資料夾僅包含不可建置的檔案,例如文件,因此請忽略其中的任何差異。」
但是,在有數百或數千個專案的繁忙單一儲存庫中,最佳化合併佇列需要精確地為專案資料夾之間細微的相依性建立模型。為此,我們正在合作制定與語言無關的 project-impact-graph.yaml 規格,合併佇列等服務可以使用該規格來查詢任何單一儲存庫中任何程式設計語言的專案相依性。透過使用 Rush 外掛程式,此 YAML 檔案將由 `rush update` 產生並提交至 Git,這可讓合併佇列服務有效率地查詢任何分支的資料夾相依性,而無需 Git 簽出。
熱門的合併佇列
建議在您的單一儲存庫中使用合併佇列。以下是一些可能的選項
- GitHub 包含一個內建的合併佇列,可以搭配或不搭配 GitHub Actions 使用
- Mergify 為 GitHub 提供附加服務,具有進階最佳化。如需設定詳細資訊,請參閱整合:將 Mergify 與 Rush 搭配使用。
- GitLab 包含一個內建的合併列車功能
如果您的組織正在使用未在上面列出的 Rush 合併佇列,請將其新增。