增強的 Git 流程解釋

已發表: 2022-03-11

使用 Git 無意中造成損壞太容易了。 然而,使用 Git 的最佳方式總是有爭議的。

這是因為 Git 本身只詳細說明了基本的分支操作,而它的使用模式(即分支模型)則取決於用戶的意見。 Git 分支模型承諾通過組織軟件開發人員對其代碼庫進行更改時不可避免地出現的混亂來減輕痛苦。

像許多開發人員一樣,您想要一些“可以正常工作”的東西,這樣您就可以繼續進行實際的軟件開發。 所以你選擇了Git flow ,這是一個經常推薦給 Git 用戶的分支模型。 也許你一開始就接受了 Git 流程的邏輯,直到你在實踐中遇到了一些障礙。 或者,Git 流程似乎不太適合您採用它。 畢竟,有無數的變量在起作用,沒有一個單一的分支模型能在所有情況下都能很好地工作。

好消息! 作為經典 Git 流模型的一種變體,增強型 Git 流簡化了 Git 流工作流中更常見的操作,同時仍保留了主要優勢。

經典 Git 流模型的輝煌與痛苦

自從我發現 Git 流在開發以顯著價值增量發展的產品(換句話說,發布)時如何表現出色以來,我一直是 Git 流的堅定擁護者。

顯著的價值增量需要大量時間才能完成,例如基於 Scrum 的開發中通常使用的兩週以上的衝刺。 如果開發團隊已經部署到生產環境,如果下一個版本的範圍累積在生產代碼所在的同一位置(例如,在他們使用的 Git 存儲庫的主分支中),則可能會出現問題。

雖然產品仍處於初始開發階段——即,沒有生產,也沒有產品的真正用戶——但團隊可以簡單地將所有內容保留在主分支中。 事實上,這還不錯:這種策略允許以最快的速度發展,而無需太多儀式。 但是在生產環境中情況會發生變化; 然後,真正的人開始依賴產品來穩定。

例如,如果生產中存在需要立即修復的關鍵錯誤,那麼開發團隊不得不回滾迄今為止在主分支中累積的所有工作以部署修復程序,這將是一場重大災難。 並且在沒有經過適當測試的情況下部署代碼——無論代碼被認為是半生不熟還是開發良好——顯然不是一種選擇。

這就是分支模型大放異彩的地方,包括 Git 流。 任何復雜的分支模型都應該回答有關如何下一個版本與人們當前使用的系統版本隔離、如何使用下一個版本更新該版本以及如何將任何關鍵錯誤的修補程序引入當前版本的問題。

Git 流程通過分離“main”(生產或“當前版本”分支)和“develop”(開發或“下一個版本”分支)並提供有關使用特性/發布/修補程序分支的所有規則來解決這些基本場景. 它有效地解決了基於發布產品的開發工作流程中的許多令人頭疼的問題。

但即使項目非常適合經典的 Git 流模型,我也遇到了它可能帶來的典型問題:

  • Git 流程很複雜,有兩個長期存在的分支,三種類型的臨時分支,以及對分支如何相互處理的嚴格規定。 這種複雜性使錯誤更有可能發生,並增加了修復它們所需的工作量。
  • Release 和 hotfix 分支需要“雙重合併”——一次進入 main,然後進入 develop。 有時你可能會忘記兩者都做。 您可以使用腳本或 VCS GUI 客戶端插件使 Git 流分支更容易,但您必須首先為參與給定項目的每個開發人員的每台機器設置它們。
  • 在 CI/CD 工作流程中,您通常會為一個發布最終構建兩個最終版本——一個來自發布分支本身的最新提交,另一個來自合併提交到 main。 嚴格來說,您應該使用主要的一個,但兩者通常是相同的,從而產生混淆的可能性。

輸入“增強的 Git 流程”

我第一次使用增強型 Git 流是在一個新建的閉源項目上。 我正在與另一位開發人員一起工作,我們一直在通過直接提交到主分支來處理該項目。

注意:在產品首次公開發布之前,為了開發工作流程的速度和簡單性,將所有更改直接提交到主分支(即使您是 Git 流程倡導者)絕對是有意義的。 由於還沒有生產,因此團隊不可能盡快修復生產錯誤。 因此,在這個階段執行經典 Git 流程所暗示的所有分支魔法是過度的。

然後我們接近了初始版本,我們同意,超過那個點,我們將不再對直接提交到主分支感到滿意。 我們的行動非常迅速,業務優先級並沒有留下太多空間來建立​​一個堅如磐石的開發流程——即,一個具有足夠自動化測試的流程,讓我們有信心保持我們的主分支處於發布就緒狀態。

這似乎是經典 Git 流模型的有效案例。 有了獨立的主分支和開發分支,並且在顯著的價值增量之間有足夠的時間,人們相信大多數手動 QA 會產生足夠好的結果。 當我提倡 Git 流程時,我的同事提出了類似的建議,但有一些關鍵的區別。

起初,我推後。 在我看來,一些針對經典 Git 流程的提議“補丁”有點太具有革命性了。 我認為他們可能會破壞主要思想,整個方法都會失敗。 但經過進一步思考,我意識到這些調整實際上並沒有破壞 Git 流程。 同時,他們通過解決上述所有痛點使其成為更好的 Git 分支模型。

在該項目中使用修改後的方法取得成功後,我在另一個封閉源代碼項目中使用了它,背後有一個小團隊,我是永久代碼庫所有者和一兩個外包開發人員不時提供幫助。 在這個項目中,我們在六個月內投入生產,從那時起,我們已經使用 CI 和 E2E 測試一年多了,每個月左右發布一次。

使用增強型 Git 流時的典型 Git 提交圖。該圖顯示了開發和主上的一些提交,以及在它們共同提交之前,幾個基於日期的標籤。

我對這種新分支方法的總體體驗非常積極,因此我想與我的開發人員分享它,以幫助他們克服經典 Git 流程的缺點。

與經典 Git 流程的相似之處:開發隔離

對於增強型 Git 流程中的工作隔離,仍然有兩個長期存在的分支,主要和開發。 (用戶仍然擁有修補程序和發布功能——強調“功能”,因為這些不再是分支。我們將在差異部分詳細介紹。)

經典的 Git 流功能分支沒有正式的命名方案。 當功能準備好時,您只需從開發分支出來並重新合併以進行開發。 團隊可以使用他們喜歡的任何命名約定,或者只是希望開發人員使用比“my-branch”更具描述性的名稱。 增強的 Git 流程也是如此。

在開發分支中累積到某個截止點的所有功能都將塑造新版本。

壁球合併

我強烈建議對特性分支使用 squash 合併,以在大多數情況下保持歷史的良好和線性。 沒有它,提交圖(來自 GUI 工具或git log --graph )在團隊同時處理少數功能分支時開始顯得草率:

將 squash 合併策略產生的提交圖與合併提交策略產生的提交圖進行比較。起始 Git 提交圖的主要分支標記為“develop”,較早的提交具有“feature-D”和“feature-C”分支,甚至更早的提交具有“feature-B”和“feature-A” “從中分出。合併提交的結果看起來很相似,但是每個功能分支都會在開發時在它綁定到它的點上導致一個新的提交。壁球合併結果已經發展為簡單的直線提交,沒有其他分支。

但是,即使您對這種情況下的視覺效果感到滿意,還有另一個理由來擠壓。 沒有壓縮,提交歷史視圖——其中包括普通的git log (沒有--graph )和 GitHub——即使是最簡單的合併場景也會講述相當不連貫的故事:

將由 squash 合併策略產生的提交歷史視圖與由合併提交策略產生的提交歷史視圖進行比較。原始 repo 狀態以時間線形式給出,顯示了來自開發上的共同提交“x”的兩個功能分支的提交時間順序,在分支之間交替提交,即按 1a、2a、1b 和 2b 的順序。合併提交結果以兩種變體形式顯示。在合併第二個分支、合併第一個分支、然後刪除兩個分支所產生的提交歷史記錄中,故事是按時間順序排列的,但不是連貫的,並且包括第一個分支的額外合併提交。在刪除之前按順序合併分支所產生的歷史記錄中,提交按順序排列,“x, 2a, 1a, 2b, 1b”,然後是第二個分支的合併提交,這甚至不是按時間順序排列的。來自 squash 合併的提交歷史對於每個功能分支只有一個提交,每個分支的故事由提交者講述。

使用 squash 合併的警告是原始特徵分支歷史會丟失。 但是,如果您使用的是 GitHub,則此警告甚至不適用,例如,它通過壓縮合併的拉取請求公開功能分支的完整原始歷史記錄,即使在功能分支本身被刪除之後也是如此。

與經典 Git 流程的區別:版本和修補程序

讓我們回顧一下發布週期,因為(希望)這是您將要做的主要事情。 當我們想要發佈在開發中積累的內容時,它嚴格來說是 main 的超集。 在那之後,經典的和增強的 Git 流之間最大的區別就開始了。

在增強的 Git 流下執行正常發佈時,Git 提交圖會發生變化。最初的圖表主要與開發提示後面的幾個提交和一個提交不同。標記後,main 和 vYYYY-MM-DD 是偶數。刪除本地 main 後,在develop、強制推送、部署、測試等的尖端創建它,main 甚至與 develop 一起顯示,將 vYYYY-MM-DD 留在 main 原來的位置。在部署/測試週期之後,在 main 上暫存修復(最終將 squash 合併到 develop 中),同時,在 develop 上進行不相關的更改,最終圖有 develop 和 main 分歧,每個都有幾個提交,從那裡他們甚至在彼此上一張圖。

增強版 Git 流程中的版本

使用增強的 Git 流程進行發布的每個步驟都不同於經典的 Git 流程:

  1. 發布是基於主要的,而不是開發的。 用有意義的東西標記主分支的當前尖端。 我採用了基於 ISO 8601 格式的當前日期的標籤,前綴為“v”,例如 v2020-09-09
    • 如果一天內碰巧有多個版本(例如修補程序),則該格式可能會根據需要在其上附加一個序列號或字母。
    • 請注意,標籤通常與發布日期不對應。 它們只是為了強制 Git 保留對主分支在下一個發布過程開始時的外觀的引用。
  2. 使用git push origin <the new tag name>
  3. 之後,有點意外:刪除你的本地主分支。 別擔心,因為我們很快就會恢復它。
    • 所有對 main 的提交仍然是安全的——我們通過在上一步標記 main 來保護它們免於垃圾收集。 這些提交中的每一個——甚至是我們將很快介紹的修補程序——也是開發的一部分。
    • 只需確保團隊中只有一個人在為任何給定版本執行此操作即可; 這就是所謂的“發布經理”角色。 發布經理通常是最有經驗和/或最資深的團隊成員,但明智的做法是避免任何特定的團隊成員永久擔任此角色。 在團隊中傳播知識以增加臭名昭著的總線因素更有意義。
  4. 在你的開發分支的提示提交處創建一個新的本地主分支
  5. 使用git push --force推送這個新結構,因為遠程倉庫不會輕易接受這樣的“劇烈變化”。 同樣,這並不像在這種情況下看起來那樣不安全,因為:
    • 我們只是將主分支指針從一個提交移動到另一個提交。
    • 一次只有一個特定的團隊成員進行此更改。
    • 日常開發工作都發生在開發分支上,因此您不會通過以這種方式移動 main 來破壞任何人的工作。
  6. 你有你的新版本! 將其部署到登台環境並進行測試。 (我們將在下面討論方便的 CI/CD 模式。)任何修復都直接進入主分支,因此它將開始與開發分支分道揚鑣。
    • 同時,您可以開始在開發分支中開發新版本,這與經典 Git 流程中的優勢相同。
    • 不幸的是,目前生產中的內容(不是即將發布的暫存版本)需要修補程序,下面的“在活動發布期間處理修補程序......”中有更多關於此場景的詳細信息。
  7. 當您的新版本被認為足夠穩定時,將最終版本部署到生產環境並進行一次 main 的 squash 合併以開發以獲取所有修復。

增強型 Git 流程中的修補程序

修補程序案例是雙重的。 如果您在沒有活動版本的情況下進行修補程序(即,團隊正在開發分支中準備新版本),這很容易:提交到 main,在暫存中部署和測試您的更改,直到它們準備好,然後部署到生產。

作為最後一步,從 main 中挑選您的提交進行開發,以確保下一個版本將包含所有修復。 如果您最終提交了多個修補程序,您可以通過創建和應用補丁而不是多次挑選來節省精力——特別是如果你的 IDE 或其他 Git 工具可以促進它。 在初始版本之後嘗試將merge main 壓縮到develop 很可能最終與develop 分支中的獨立進度發生衝突,因此我不建議這樣做。

在活動版本期間處理修補程序(即,當您只是強制推送主版本並且仍在準備新版本時)是增強型 Git 流程中最薄弱的部分。 根據您的發布週期的長度和您必須解決的問題的嚴重性,始終致力於在新版本本身中包含修復——這是最簡單的方法,並且根本不會破壞整個工作流程。

萬一這是不行的——你必須快速引入一個修復,並且你不能等待新版本準備好——然後準備一個有點複雜的 Git 過程:

  1. 創建一個分支——我們稱之為“新發布”,但你的團隊可以在這裡採用任何命名約定——在與 main 的當前提示相同的提交處。 推送新版本。
  2. 在您之前為當前活動版本創建的標記的提交處刪除並重新創建本地主分支。 強制推主。
  3. 對 main 引入必要的修復,部署到暫存環境並進行測試。 只要準備好了,就部署到生產環境中。
  4. 通過挑選或補丁將當前主版本的更改傳播到新版本。
  5. 之後,重做發布過程:標記當前主幹的尖端並推送標記,刪除並在新發布分支的尖端重新創建本地主幹,並強制推送主幹。
    1. 您可能不需要以前的標籤,因此您可以將其刪除。
    2. new-release 分支現在是多餘的,因此您也可以將其刪除。
  6. 您現在應該可以像往常一樣使用新版本了。 通過從主要傳播緊急修補程序以通過櫻桃採摘或補丁進行開發來完成。

在增強 Git 流程下的活動版本期間執行修補程序時,Git 提交圖會發生變化。起始圖已發展為最長的提交線,主要將兩個提交前一個提交分叉,而在此之前的三個提交,a 分支分一個提交,標記為 v2020-09-18。在上面的前兩個步驟之後,該圖在 main 曾經是新版本的地方,而 main 甚至沒有 v2020-09-18。然後執行其餘步驟,生成最終圖表,其中 main 是在 new-release 之前的提交,而 v2020-09-20 是在 v2020-09-18 之前的提交。

有了適當的計劃、足夠高的代碼質量以及健康的開發和 QA 文化,您的團隊不太可能不得不使用這種方法。 為了以防萬一,為增強 Git 流程開發和測試這樣的災難計劃是明智的——但我從來不需要在實踐中使用它。

基於增強型 Git 流程的 CI/CD 設置

並非每個項目都需要專用的開發環境。 在每台開發人員機器上設置複雜的本地開發環境可能很容易。

但是,專門的開發環境有助於建立更健康的開發文化。 在開發分支上運行測試、測量測試覆蓋率和計算複雜性指標通常可以通過在錯誤最終進入暫存之前及時發現來降低錯誤成本。

我發現一些 CI/CD 模式在與增強的 Git 流程結合使用時特別有用:

  • 如果您需要開發環境,請設置 CI 以在每次提交到開發分支時對其進行構建、測試和部署。 如果你有它並且它對你的情況有意義的話,也適合這裡的 E2E 測試。
  • 設置 CI 以在每次提交到主分支時構建、測試和部署到暫存環境。 在這一點上,E2E 測試也非常有益。
    • 在這兩個地方使用 E2E 測試似乎是多餘的,但請記住,在開發中不會發生修補程序。 在提交到 main 時觸發 E2E 將在發布之前測試修補程序日常更改,但也觸發提交到開發將更早地捕獲錯誤。
  • 以允許您的團隊根據手動請求將構建從主環境部署到生產環境的方式配置 CI。

當除了暫存(“stage”)和生產(“prod”)之外還有一個開發環境(“dev”)時,推薦的 CI/CD 設置和增強的 Git 流程。開發分支上的所有提交都會導致構建部署到開發。同樣,所有對 main 的提交都會導致構建部署到階段。來自 main 的特定提交的手動請求會導致構建部署到生產環境。

這種模式相對簡單,但提供了強大的機制來支持日常開發操作。

增強的 Git 流模型:改進和可能的限制

增強的 Git 流程並不適合所有人。 它確實利用了有爭議的武力推動主要分支的策略,因此純粹主義者可能會對此感到不滿。 不過,從實際的角度來看,這並沒有什麼問題。

如前所述,修補程序在發布期間更具挑戰性,但仍有可能。 適當注意不應經常發生的 QA、測試覆蓋率等,因此從我的角度來看,與經典 Git 流相比,增強 Git 流的整體優勢是一個有效的權衡。 我很想知道在大型團隊和更複雜的項目中如何增強 Git 流票價,其中修補程序可能更頻繁地出現。

我對增強型 Git 流模型的積極體驗也主要圍繞閉源商業項目展開。 對於拉取請求通常基於源代碼樹的舊版本派生的開源項目來說,這可能是有問題的。 沒有技術障礙可以解決這個問題——它可能需要比預期更多的努力。 我歡迎在開源領域有豐富經驗的讀者提供關於增強 Git 流在這種情況下的適用性的反饋。

特別感謝 Toptal 的同事 Antoine Pham 在開發增強 Git 流程背後的想法方面發揮的關鍵作用。


進一步閱讀 Toptal 工程博客:

  • 基於主幹的開發與 Git 流程
  • 專業的 Git 工作流程:一個好的 Git 指南

Microsoft 金牌合作夥伴徽章。

作為 Microsoft 金牌合作夥伴,Toptal 是您的 Microsoft 專家精英網絡。 與您需要的專家一起建立高績效團隊 - 在您需要的任何時間和地點!