隨著鐵人賽即將完賽,對於整系列的結構也逐漸清晰。雖然很想繼續針對 GitLab CI 的部分去探討,但仍決定先回頭講聊聊與 Git 原理比較相關的主題,將系列缺漏的部分補上。今天就讓我們聊聊 Git 是如何將鬆散的儲存壓縮成較有效率的儲存結構吧。
—— Day 27
正如前面〈Git Commit〉與〈Git Object〉所述,Git 會將每一個版本像照相一樣拍下來,每一個檔案都會儲存成 Git Blob Object,無論這個檔案是否與前一個版本相差無幾,對 Git 來說都是不同的物件,所以會原封不動的儲存下來,而不是像其他版本控制工具一樣是儲存兩個檔案之間的差異。儘管這樣的方式帶給我們許多好處,例如切換版本速度快、構成 Git 分散式版本控制的基礎等等,但是長久下來 Git 的資料庫不免逐漸笨重起來。
這個顯而易見的議題,Git 當然不會忽視,本章就來聊聊 Git 是如何解決這個議題的。
2-1. Garbage Collect
事實上,Git 會不定期進行「垃圾收集」(garbage collet,gc) 的動作,觸發條件是 7,000 個左右的 loose object(即沒有被壓縮成 packfile 的 Git Object)或是 50 個 packfile。而這個動作主要會做幾件事情:
將所有有被參考的 loose objects 封裝成 packfiles
將較小的 packfiles 合併成一個大的 packfiles
將所有的 Git Reference 檔案合併成
packed-refs
檔案
將不被參考的 Git Objects 刪除,像是:
沒有被任何 Tree Object 參考的 Blob Object
沒有被任何 Commit Object 和 Tree Object 參考的 Tree Object
沒有被任何 Git Reference 參考的 Commit Object
透過這些事情,就能將 Git Repository 所需要的空間縮小了。
# Command Synopsis: git-gc
# Reference: https://git-scm.com/docs/git-gc
git gc [--options]
OPTIONS
--aggressive
--auto
--quiet
--prune=<date> | --no-prune
--force
--keep-largest-pack
2-2. Pack Loose Objects
那麼 Git 又是如何將這些 loose objects 封裝成 packfiles 的呢?Git 會去尋找檔案名稱與大小相近的檔案,並且只保存檔案不同版本之間的差異內容,如此就能兼具那些儲存差異的版本控制節省空間的優點。而為了提升效能,在封裝成 packfile 的同時,也會建立該 packfile 的 index 檔案,如此 Git 在尋找 Git Object 時,就能直接透過 index 快速找到該 Git Object 在 packfile 的位置以進行還原。
每一個 packfile 都會對應到一個 index,因此通常會有一組這樣命名的檔案:
pack-<SHA-1>.pack
pack-<SHA-1>.idx
詳細可以透過 git verify-pack
指令去查看指定的 packfile 儲存了哪些 Git Object 去深入暸解
# Command Synopsis: git-verify-pack
# Reference: https://git-scm.com/docs/git-verify-pack
git verify-pack [--options] <pack>.idx
OPTIONS
--verbose
--stat-only
OUTPUT FORMAT
SHA-1 type size size-in-packfile offset-in-packfile depth base-SHA-1
2-3 Pack Refereces
除了 loose objects 和 packfiles 外,前面也提到所有的 Git Reference 檔案會合併成 packed-refs
檔案,該檔案的格式大概如下:
# pack-refs with: peeled
<SHA-1> <Reference Path>
<SHA-1> <Reference Path>
所以當某個 Reference 不存在於 .git/refs
目錄底下時,Git 便會去 .git/packed-refs
檔案中尋找。但若是我們更新某個 Reference,例如進行了 commit,所以當前 branch 對應的 commit SHA-1 值被更新了,此時 Git 是不會主動更新 packed-refs
中的值,而是在建立一個 Git Reference 檔案,直到 Git References 再度被後並成 packed-refs
檔案。
若是想直接手動進行合併,也可以直接輸入 git pack-refs
指令進行合併。
# Command Synopsis: git-pack-refs
# Reference: https://git-scm.com/docs/git-pack-refs
git pack-refs [--options]
OPTIONS
--all
--no-prune