化整為零搞定超巨大commit

📅 July 1, 2021 💬 2
PS C:\workdata> git push origin main:refs/heads/main
Enumerating objects: 5973, done.
Counting objects: 100% (5973/5973), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3984/3984), done.
Writing objects: 100% (5971/5971), 2.46 GiB | 1.96 MiB/s, done.
Total 5971 (delta 1451), reused 5968 (delta 1449), pack-reused 0
error: RPC failed; HTTP 502 curl 22 The requested URL returned error: 502
fatal: the remote end hung up unexpectedly
fatal: the remote end hung up unexpectedly
Everything up-to-date

序幕

你是否也曾在某處看過這樣的訊息?看完忍不住一陣咒罵、然而挖坑同事早已收拾閃人?

先前處理到一個git repository遷移任務,原主機位置不明、配置不明、管理員不明,不只頻繁卡頓還發生專案丟失的現象,時間緊湊只得用最土炮的方式直接clone到本機。複製完成後,因為操作最熟悉而選擇了GitLab做為新的存放位置。

本來平平都是git要遷移起來應該是沒難度的,然而原先的repo裡面從原始碼到IDE、從會議記錄到訪談錄音無所不包,前一手離職前還把git當網路硬碟將另一個專案也commit進來。多達4GB的repo在傳輸上隨便都用小時計算,就在這邊發生我始料未及的狀況: GitLab的執行時限太短。

網路上簡單搜索一下可以發現不少類似問題,雖然錯誤碼不一但共同點是每個repo都很大。多數人看到這邊的直覺反應應該是拆成多次push分批上傳,而我一開始也是這麼做。不過分批仍有極限,當單單一個commit的大小就有數GB的時候,即使一次只push一個commit也會落到逾時結果。

既然一個commit太大,那分成兩個不行嗎?普通情況下,這時候應該會開始動念修改commit,將一個commit切成多個之後再分批上傳。想法不錯,是我也會這樣做順便把過於巨大的檔案清一清,但是只要修改就會讓hash改變,在這個情境下很遺憾不可行。

WTF!水管就那麼大,這不是無理取鬧嗎?

推理

其實還真有可能。這邊先來回顧一下git的基本概念:

  • Object ID: git使用hash來識別所有物件,包含blob、tree、hash
  • Blob: 真正的檔案會被保存為一個個的blob,每個blob經過hash後歸檔
  • Tree: blob只有檔案內容,而檔名和路徑則另外儲存,並透過參照blob或其他tree的hash來遞規組裝出目錄結構。透過這個方式,相同檔案只需要保留一份
  • Commit: 將tree的原點、parent commit和作者資訊一起保存,如此一來便可將一堆互不相關的tree關聯出時間先後

也就是說git基本單位的commit裡面還有更多次級結構存在,而且這些結構是可以被重複使用的。

這給我一個想法,如果我可以把這些blob打散從水陸空分開送上去,最後再集結起來組成commit是否可行?原本卡關的單一commit,往下分析之後發現他只是blob特別多,但是每個blob都在可接受範圍內,這個賭注勝算很大。

實踐

設卡關commit的位置為DEADBEEF~5,我們要把它的內容分散成一堆小commit,而且必須確保這些blob沒有改變。

git checkout -b CHIMAME DEADBEEF~5
git reset HEAD~1

如此一來便將該commit內容展開到worktree當中,準備好重新編組。接著我們進行幾次commit,每次commit要控制在幾百MB的大小。

當所有內容都commit完畢記得diff確認剛剛長出來的分支CHIMAME和目標的DEADBEEF~5狀態是否一樣。趁機上傳更多檔案不是不行,不過這會增加複雜度所以不討論。

git add file01 file02 file03 ... file99
git commit -m ""

git diff CHIMAME DEADBEEF~5

應該比對不到差異才是,有東西自己檢討。commit完還要讓git在之後push DEADBEEF的時候能夠重複使用這些blob,為此我將DEADBEEF~5合併進剛剛建立的CHIMAME分支去。

git merge DEADBEEF~5

這會讓CHIMAME領先DEADBEEF~5一個commit,而DEADBEEF~5因為被合併成為目前歷史的一部分也會順勢帶上去,大概是這種感覺:

  *    CHIMAME (parent: CHIMAME~1, DEADBEEF~5)
 /|
* |  DEADBEEF~5
| |
| *    CHIMAME~1
...
| *    CHIMAME~15
| |
| *    CHIMAME~16
|/
*    DEADBEEF~6

全部準備完成之後就要開始分批上傳,這邊會需要點時間,開始執行後可以放著先去吃飯。

for($i=16; $i -ge 0; $i--) {
  git push origin CHIMAME~$i`:CHIMAME
}

執行完畢以後,讓你牙癢癢的commit DEADBEEF~5已經乖乖躺在GitLab上面了,再來就按正常流程把剩下的正常commit也push上去。

git push origin DEADBEEF

不改變hash的情況推送超大commit,任務完成😎


Comments

Terri's gravatar

If yoս are going for finest cօntents ⅼike Ι do, јust visit thіѕ site daily as itt presents quality cοntents, tһanks

Ambertany's gravatar

This document describes how to use the dynamic debug (dyndbg) feature Dynamic debug

Post comment

Please don't duplicate submitting.