離線同步 Git 版控記錄實踐:git format-patch 和 git bundle 的差異

模擬情境

公司有 Sam 和 Jack 兩位開發者,用公司內部 Git 版控儲存庫進行開發。

簡述版控流程,開發者先在自己的分支開發,再將結果合併回 Develop 分支驗測功能,驗證無誤再透過 PR 程序合併至 master 分支上線。

公司內部版控儲存庫不提供任何管道給外部網路連線使用,但有開放 Webex 可遠端連線進去控制公司內部的電腦。

如下圖,假設公司在 commit e3251c8 時開始實施 Work From Home,從這裡開始就無法再連線至公司內的遠端儲存庫。

image001.png

實施 Work From Home 後,Sam 開始修改本地 Develop_Sam 分支的 README.md 檔案內容,並且合併回本地的 Develop 分支。

image003.png

同時,Jack 也在修改本地 Develop_Jack 分支的 README.md 檔案內容,也合併回本地的 Develop 分支。

image005.png

前兩張圖可看到 Sam 與 Jack 在本地 Develop 分支的版控記錄 (Commit ID) 已經不一致了,雙方修改過的 README.md 檔案內容如下,之後若要合併肯定會發生衝突。

image007.png

接下來 Sam 和 Jack 要同步彼此的開發進度,但是連不到公司的版控儲存庫了,此時有以下幾種選擇。

可用的方法

  1. 使用外部私有 Git 遠端儲存庫同步彼此的版控記錄 (公司沒有禁止的情況適用)

    • 此做法無法解決上版至公司內部儲存庫的問題,只能解決開發者間的版控同步問題 (但光是解決這個問題,就可省下大半的時間了)

    • 公司不一定允許 Source Code 放在外部的私有儲存庫

  2. 將 Source Code (包含 .git 目錄) 通通打包傳給其他開發者或公司 (你瘋了嗎? 😆)

    • 每次交付的檔案都很大包,就算只改一個字,要上版都要整包傳輸,我實在想不到這個做法的優點…
  3. git bundle 產生版控記錄檔 (★ 推薦)

    • 會產生一個容量很小的 binary 檔案利於傳輸,檔案大小取決於要包含的版控記錄多寡,裡面會有修改的檔案內容,以及 commit 版控記錄

    • 本地端可透過 git pull 將記錄檔的版控內容 pull 合併至本地分支,並且保持開發的版控記錄,也可以送入公司同步版控記錄

    • 可把 git bundle 當成原本 git push 的替代操作,只是變成離線版

  4. git format-patch 產生內容修補檔 (只適合交付修補程式用,不適合開發過程同步版控用)

    • 開發者可套用修補檔以更新彼此修改過的檔案內容,也可送入公司上版,但是在開發過程中,每個人的版控記錄會不一致

    • 修補檔記錄的是 git diff 的差異內容,檔案也不大,但不會保留版控記錄,套用修補檔時,背後是透過 git commit 在處理,會產生新的版控記錄 (commit hash)

    • 版控記錄脫鉤,會造成查找問題或溝通上的困難,將來連回公司內部的版控儲存庫時,有可能要再面臨大量合併衝突的問題

    • git format-patch 只適合交付修補程式用,不適合開發過程同步版控用,若不打算同步版控記錄就沒差了

為了節省大家寶貴的時間,先說結論

  • 如果公司禁止使用私有儲存庫

    • 開發過程的版控同步
      每位開發者的修改告一段落,就用 git bundle 產生 Develop 分支的版控記錄檔,交付給其他開發者同步 Develop 分支的版控記錄

    • 功能開發完成,要回傳公司上版
      git bundle 產生 Develop 分支的版控記錄檔,只是這次要傳進公司,遠端遙控公司內部電腦操作 git pull 將版控記錄檔合併到 Develop 分支,驗測無誤後再 PR 合併至 master 分支

  • 如果公司同意使用私有儲存庫 (★ 個人認為這是最佳狀況)

    • 開發過程的版控同步
      使用私有 Git 儲存庫進行開發,只有在這種非常時期才深刻體會到可以 git push 是幸福的 😆

    (安全性要記得做好,否則 Source Code 外流就……會變成 Open Source 😆 )

    • 功能開發完成,要回傳公司上版
      git bundle 產生 Develop 分支的版控記錄檔,只是這次要傳進公司,遠端遙控公司內部電腦操作 git pull 將版控記錄檔合併到 Develop 分支,驗測無誤後再 PR 合併至 master 分支

接下來說實作,有興趣的人請繼續看下去

實作方式

私有 Git 遠端儲存庫

這個做法就不說明了,有免費的、付費的、私有自架的…路很多條,任君挑選。

接下來,為了節省大家寶貴的時間,先說個人推薦的 git bundle 實作方式

用 git bundle 產生版控記錄檔 (★ 推薦)

先把時光回朔到公司開始實施 Work From Home,並且 Sam 和 Jack 都在自己的本地分支修改過檔案的狀態

image009.png

先從 Sam 的角度開始,假設 Sam 已經完成自己負責的功能開發,準備要將完成的結果 Develop 分支 push 給 Jack,但現在沒有遠端儲存庫可以 push,所以改用 git bundle 來實現離線同步。

從 Sam 的本地版控記錄可看到 Jack 在 Develop 分支有 commit e3251c8,這是雙方 Develop 分支都有的 commit 可以作為合併的基準點。

Sam 透過 git bundle 產生從 e3251c8 到本地分支 Develop 範圍的版控記錄檔,檔案命名為 Develop_FromSam,檔案會產生在工作目錄內,將這個檔案交給 Jack。

1
git bundle create Develop_FromSam e3251c8..Develop

image011.png

Jack 收到檔案 Develop_FromSam 先用 git bundle verify 檢查檔案內容

1
git bundle verify Develop_FromSam

圖中可看到,此檔案內含 Develop 分支的版控記錄,並且只要本地端有 commit e3251c8 就可以進行 git pull 合併。

image013.png

Develop_FromSam 檔案想像是一個遠端儲存庫,裡面有一個 Develop 遠端分支。

Jack 現在想把遠端 Develop 分支 pull 合併到本地的 Develop 分支。

和平常 pull 流程一樣,先 checkout 到本地 Develop 分支,再 git pull 遠端儲存庫的 Develop 分支。

如果 checkout 錯分支,這個 git pull 會合併到錯的分支上,這和平常 pull 行為是一樣的,只是現在遠端儲存庫換成版控記錄檔。

1
2
git checkout Develop
git pull Develop_FromSam Develop

因為 Sam 在遠端 Develop 分支和 Jack 在本地都修改到同一個 README.md 檔案,此時合併發生衝突進入 MERGING 狀態。

image015.png

透過 git statusgit status -s 確認發生衝突的是 README.md 檔案,下圖 Untracked files Develop_FromSam 檔案可以無視,這個檔案在 git pull 合併完成後就可以刪掉了。

image017.png

打開 README.md 看到 Git 版控提示的衝突內容

image019.png

修正衝突並存檔

image021.png

將修正的 README.md 檔案加入 git 索引,再透過 git commit 提交,合併完成了

1
2
git add README.md
git commit

image023.png

Jack 的合併已經完成,因為有修正衝突,所以合併會產生新的 commit,將合併後的本地 Develop 分支也透過 git bundle 輸出版控記錄檔 Develop_FromJack 給 Sam 離線同步用。

上面這一步可以想成是原本在做 git push 的動作,只是目前沒有 Git 遠端儲存庫可以 git push,所以這類動作現在都改用 git bundle 代替

從 Jack 的本地版控記錄可看到 Sam 在 Develop 分支有 commit e3251c8,這是雙方 Develop 分支都有的 commit 可以作為合併的基準點。

Jack 透過 git bundle 產生從 e3251c8 到本地分支 Develop 範圍的版控記錄檔,檔案命名為 Develop_FromJack,檔案會產生在工作目錄內,將這個檔案交給 Sam。

1
git bundle create Develop_FromJack e3251c8..Develop

image025.png

Sam 收到檔案後先確認版控記錄檔內是否有 Develop 遠端分支和合併時必要的 commit e3251c8

因為 Jack 已經解決合併衝突,所以 Sam 的本地 Develop 分支直接 git pull 就完成合併了,此時 Sam 與 Jack 的 Develop 分支記錄都是一致的。

小提醒:git pull 前要記得切對分支,此例 Sam 要 checkout Develop

完成後即可將 Develop_FromJack 檔案刪除。

image027.png

下圖可見,現在 Sam 和 Jack 的版控記錄是一致的,如果要送進公司上版,也可以直接把 Develop_FromJack 檔案傳進公司,再遠端遙控公司內部電腦透過 git pullDevelop 合併進去上版。

因為大家的版控記錄都是維持同步的狀態,將來 Work From Home 結束,回公司連上內部儲存庫,也是無縫銜接。

image029.png

接下來說明如何實作 git format-patch,但這個做法我不推薦,不想看的人可以直接跳過。

用 git format-patch 產生內容修補檔 (不建議開發用)

先把時光回朔到公司開始實施 Work From Home,並且 Sam 和 Jack 都在自己的本地分支修改過檔案的狀態

image031.jpg

Sam 使用 git format-patch 產生 e3251c84df1ae8 之間的內容修補檔,並指定檔案輸出的目錄路徑 D:/Temp/GitTest/Develop_PatchFromSam/

1
git format-patch e3251c8..4df1ae8 -o /d/Temp/GitTest/Develop_PatchFromSam/

目錄 D:/Temp/GitTest/Develop_PatchFromSam/ 內產生了兩個修補檔,傳送給 Jack

image032.png

Jack 收到 Sam 給的內容修補檔,使用 git am 將修補內容 commit 至本地的 Develop 分支

1
git am [修補檔路徑]

因為雙方 Develop 分支的 README.md 檔案都有修改,發生衝突了

聰明的 Sam 早早寫完先交付就沒事了,寫得慢的 Jack 就要解衝突,陷入被越拖越慢的惡性循環 😆

image034.png

見下圖右上方,目前狀態進入 Develop|AM 處理模式了,倒楣的 Jack 先看看是什麼造成衝突

1
git am --show-current-patch=diff

image036.png

把造成衝突,缺少的內容補進 README.md,並存檔

image038.png

把修改的結果加入 git 索引

1
git add .

問題排除,繼續執行 AM

1
git am --continue

第二個修補檔也發生衝突了,Jack 在心裡咒罵 Sam

image040.png

再看看這次的衝突原因

image042.png

再次把造成衝突,缺少的內容補進 README.md 並存檔

image044.png

把修改的結果加入 git 索引

1
git add .

問題排除,繼續執行 AM

1
git am --continue

見圖右方 Develop|AM 狀態結束,總算合併完成了

image046.png

現在 Jack 本地 Develop 分支多了兩個 commit 將 Sam 修改的內容併進來了

image048.png

等等,做到這邊,有人注意到 Sam 還沒拿到 Jack 合併後的結果嗎?

所以 Jack 要再 git format-patch 產生 Develop 分支的修補檔回去給 Sam

而 Sam 將 Jack 給的修補檔再透過 git am 合併到自己本地 Develop 分支,又再產生新的 commit,大家的版控記錄越來越不一致…

在可以連到遠端儲存庫同步之前,大家的 Commit ID 都對不起來,有時會造成溝通或追查問題的困難,即使連到遠端儲存庫了,也免不了要再解決合併衝突

由此看來 git format-patch 並不適合開發過程的版控交換用途,他的重點是檔案的修補,做為交付修補程式的一種方式,而非版控記錄

image050.png

以上是小弟個人的理解和實務操作經驗分享,如果有錯誤的地方還請不吝指點,謝謝 😊