[軟體概念入門系列] Git與版本控制

Brett Yu
Brett’s dev log
Published in
8 min readNov 4, 2023

本節介紹Git, 版本控制與公司採用的版本控制策略

集中式版本控制系統

以前較主流的版本控制系統, 如VSS, TFS, SVN等都是屬於這類型的架構, 所有的變更歷史記錄都儲存在伺服器中, 當伺服器發生問題的時候所有使用者都會無法進行異動分支及檔案, 也無法查看歷史記錄, 若伺服器上的資料遺失, 則很難透過還原伺服器備份檔案之外的方式來回復

分散式版本控制系統

Git屬於分散式版本控制系統, 所有的使用者同步過後都會在本機上有一份完整的檔案歷程, 亦即每個使用者都會有與伺服器相同的資料, 而使用者也可以自行在本機上建立分支或進行其他檔案異動, 僅在需要跟伺服器同步的時候才會跟伺服器建立連線, 因此若伺服器中斷服務, 使用者可以較不會受到影響, 若伺服器資料遺失, 則可以用任何一個使用者本機有同步到最新的檔案來還原資料

Repository

版本控管的一個最大單位, 若將整個根目錄資料夾作為一個Repository, 則即使只異動其中一個檔案產生一個版本, 該版本也會儲存整個根目錄以下所有的子資料夾及檔案的當下版本, 因為這個版本是整個Repository的版本, 只是此版本與上一版相比只異動了一個檔案

Commit

在版本控管系統中檔案每次修改送出, 都會產生一個新的版本並有對應的Commit, 在git中每個Commit都會有自己獨一無二的識別ID

以下為Git操作的流程, 本章不會介紹到Staging Area的概念, 請有需要操作Git的讀者再自行查閱

Branch

分支是從檔案的某個Commit開始建立一個平行宇宙的副本, 使用者可以從這個副本開始做後續的修改, 且後續修改的每個Commit都不會影響到原本分支的後續修改, 直到使用者決定把這個新Branch上的Commit套用到原本的Branch上

master (main) branch

master是一個預設的branch, 在Repository建立當下就會產生的branch, 實際上與其他branch並沒有不同, 也會因為不同的分支策略代表不同的含意, 如做為開發中的版本或代表目前正式環境的版本

由於政治正確的考量要消除master/slave的字眼, 故近期主流的版控平台已將預設的branch名稱改為main

release branch

在公司內代表對應目前Production版本的程式碼, 用來作為hotfix使用

Tag

使用者可以在指定的Commit上加上一個自訂的Tag, 相較Commit ID來說比較容易識別跟記憶

Merge, Cherry-pick, Rebase與Conflict

Merge是將指定的Branch上的所有變更套用到目前所在的Branch, 套用後會產生新的Commit在目標Branch上, 因為Merge也是版本變更動作的一種

Cherry-pick是透過指定Commit ID指定特定變更要套用在目前所在的Branch, 套用後同樣會產生新的Commit

Rebase則是修改目前Branch初始的分岔點, 改為從指定的Branch重新分岔, 再依序重新套用目前Branch的Commit中的所有變更, 這個動作可以視為竄改歷史, 因為所有的Commit都會在套用當下重新產生變更時間及Commit ID, 因此若有多人共用Branch務必要做好確認才能進行Rebase

Conflict是在做上述三種操作的時候, 若有檔案的相同位置在兩邊版本都有被異動, 系統無法判斷該使用哪一個變更時會需要使用者介入排除

(系統判斷並不一定永遠正確, 請養成每次Commit前都檢查檔案差異的好習慣)

Revert, Reset

Revert是取消某個Commit, 實際的行為再新增一個全新的Commit復原變更, 因此最終歷程中會有修改過, 被復原的兩個Commit

Reset也是取消某個Commit, 實際的行為則是修改歷史記錄抹消掉該Commit, 故最終歷程中不會有相關的變更記錄, 同樣是多人共用Branch時需要確認才能執行的動作

Fork

與Branch的概念類似, 從某個時間點建立平行宇宙的副本, 只是Fork的單位是Repository

已Fork出去的Repository仍可以與原始Repository (通常稱為Upsteam)透過Merge方式雙向同步, 若兩個Repository差異越來越大, 通常也會越來越難以同步

分支策略

目前使用的分支策略為Git Flow的變體, 所有的異動原則上從master出發, 且不建立對應各環境的Branch來對應當下環境版本, 僅用release branch來代表Production版本, 主要的原因如下

  • 希望部署到各環境的程式都是同一包Package, 以確保QA驗證過後該版本不會再有異動
  • 在開發及測試過程中原則上不會抽掉已合併的變更, 故每個版本可能會帶著已知問題至Production
  • 承上, 若有嚴重問題以及時修正或延後交付兩種做法為主

下圖為單人及多人開發時的範例, 多人開發時會回到共用的branch再一起回到master

下圖為各環境Normal Release, UAT Patch以及Hotfix的流程

我們會對要上版的Commit下Tag, 代表該版本的版本號及是在哪一天建立的, 版本格式種類及說明如下

Website, Worker, Api版本號

  • 1.0.2-beta220506 代表1.0.2版本的測試版且是在2022/05/06建立的第一版
  • 1.0.2-beta22050601 代表1.0.2版本的測試版且是在2022/05/06建立的第二版
  • 1.0.2-release 代表1.0.2已通過QAT及UAT測試, 要上到Production的確定版本
  • 1.0.2-patch220512 代表1.0.2-release版本已產生後, 但尚未上到Production前在Staging找到額外問題, 要修復此問題的版本
  • 1.0.2-hotfix220515 代表1.0.2-release版本已上到Production且發生嚴重問題需要馬上處理, 要修復此問題的版本

App版本號

  • x.y.z x => api 完全不相容 或是 內容大幅度變動 y => 功能變更 通常是 有 story 或是 change request , optimize z => bug fix 或是 一些 refactor 以及 一些不影響功能的調整
  • 2.8.4-dev+1256 代表2.8.4的開發版本, build號是1256
  • 2.8.4-rc+1256 代表2.8.4的待驗證版本, build號是1256
  • 2.8.4-release+1256 代表2.8.4的正式版本, build號是1256

Fix Merge Back:

下一個版本已經在開發中, 但在較舊版本的環境中發現問題且需要馬上修正 (UAT Patch, Hotfix), 由於修正會使用舊版本來建立新分支並只修正該分支, 這時候需要將此修正也套用到開發中的分支否則新版本也可能發生一樣的問題, 此動作通常稱為Merge Back 實際上除了merge, cherry pick這幾種方法外, 也有可能因為新分支的該部分功能有其他異動, 必須要解決衝突或甚至重新開發修正才能做到Merge Back

Pre-Production環境:

圖中沒有畫到此環境及相關流程僅用文字說明, Pre-Production是Hotfix的驗證環境, 因此Pre-Production環境的版本應永遠跟Production相同

  • 在Normal Release到Production之後, 應馬上將同樣的版本上至Pre-Production
  • 在Hotfix上到Production前, 應先在Pre-Production驗證無誤後才能上到Production

--

--