方案對比
subtree
使用命令 git subtree split -P dirPath -b branchName
將目標文件夾的代碼都保存到指定分支。試了下,該方案雖然保留了 commit,但是所有分支全都沒了
filter-branch
git filter-branch --prune-empty --subdirectory-filter dir1 -- --all
--prune-empty
:表示如果修改后的提交為空則扔掉不要
--subdirectory-filter
:指定子目錄路徑
-- --all
:針對所有的分支
當上述命令執行完畢后,就可以看到本地的新倉庫已經是原倉庫子目錄中的內容了,且保留了關於該子目錄所有的提交歷史。看了下倉庫大小和操作前沒有變化,因為 .git 目錄里還保留着無用的 object。還需要清理一下無用文件。下面來實操一下 filter-branch
怎么拆分倉庫。
實戰
模擬 git 倉庫
為了比較好追蹤問題,我們先模擬出一個 git 倉庫,這樣數據量小,排查起來會比較方便。
看一下 objects 文件夾:
在操作之前,先看一下倉庫大小,使用 git count-objects -v
命令可以計算倉庫大小:
count: 27
size: 31528
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0
查找倉庫里的大文件
使用下面的命令可以查找倉庫中的大文件:
git rev-list --all --objects | \
grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -n 3 | awk -F ' ' '{print $1}')"
大概說明一下上面腳本使用到的命令:
git rev-list
:查看指定對象的文件路徑、文件名
git rev-list --objects --all |grep a6d75f
//將列出文件路徑
a6d75f7315534b7cfe73597f1ae0f388b9494332 aaa/Main.java
git verify-pack
:查看 git 打包文件的信息,輸出 SHA-1、size 等字段。這里用到 $ 先保存 git verify-pack
的輸出。
如果直接使用上述腳本會發現什么也沒輸出,因為腳本首先是分析 git 打包文件,然后再進行大小排序的。所以我們需要先使用命令 git gc
打包 git 本地目錄存儲的文件。
執行 gc 命令后,blob 對象會被打包,再查看 objects 目錄可以看到創建了一個包文件(pack 文件)和一個索引文件。Git 會對大文件進行打包,生成 .pack
格式的文件以及同名的 .idx
格式的索引文件,存放在 .git/object/pack 目錄中。通常來說,Git倉庫的大文件都是.pack格式的,存放在這個目錄中。
gc 后,我們再看一下倉庫大小:
count: 0
size: 0
in-pack: 27
packs: 1
size-pack: 30671
prune-packable: 0
garbage: 0
size-garbage: 0
可以看到比 gc 之前稍稍少了一點。現在可以列出 idex 索引文件中存儲的文件:
git verify-pack -v .git/objects/pack/pack-e3b9f038d3df8b6214fef04e37477f98a0b48911.idx
690898ddaf1b386531b9c1c81eec09eb18f4efa8 commit 208 166 12
2c9790dbda1a83f64c7c7a8660f11aea87e14e78 commit 215 168 178
c4111b93fc90f70885dc71eae6be44811658197c commit 209 163 346
0c5e8cf09a2574a38e26c1c7cb0361f8e3b7f2b1 commit 218 175 509
50b29eb5e83cf486a72c3dfa6f0ba5ee3b2b2e32 commit 214 166 684
f05893bf834b85795e8e592bbe51130bc1792ce1 commit 252 192 850
dbd772576def32be5be8aa55fdbc2e5143551d5d commit 157 117 1042
2095176e6f0116345c3fe223f724deb057ee8b73 blob 166 136 1159
...
上述每行里各項值分別對應着:
SHA-1, type, size, size-in-packfile, offset-in-packfile
執行 filter-branch
filter-branch
需要指定目錄,拆分倉庫時可能需要同時保留多個目錄,可以使用以下腳本(這里僅保留 aaa,如果填的是 aaa bbb,即可同時保留 aaa 和 bbb 文件夾):
git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- aaa' --prune-empty -- --all
執行完命令后,git 圖表變成:
可以看到,與 aaa 文件夾下文件無關的 commit 都被移除了。分支也都被保留了,棒棒的!正是我們想要的效果。再看保留下來的各個 commit 的內容:
可以看到,雖然 commit 的提交信息沒有變,但是內容卻變了,first commit 原先新增了 aaa/Main.java 和 bbb/Main.java,但是操作后,提交記錄里只剩下了 aaa/Main.java。內容變化了,同樣的 SHA1 值也變化了。
再查看一下倉庫大小:
count: 10
size: 40
in-pack: 27
packs: 1
size-pack: 30671
prune-packable: 0
garbage: 0
size-garbage: 0
額,size-pack 的大小沒有變化,我們直接查看一下 objects 目錄。
雖然工作目錄中不需要的文件已經被清除了,但是 git/objects/pack 目錄里存儲的 pack 文件和索引文件卻沒有被刪除,重新讀取一下 idx 文件的索引:
git verify-pack -v .git/objects/pack/pack-e3b9f038d3df8b6214fef04e37477f98a0b48911.idx
——————
690898ddaf1b386531b9c1c81eec09eb18f4efa8 commit 208 166 12
2c9790dbda1a83f64c7c7a8660f11aea87e14e78 commit 215 168 178
c4111b93fc90f70885dc71eae6be44811658197c commit 209 163 346
0c5e8cf09a2574a38e26c1c7cb0361f8e3b7f2b1 commit 218 175 509
50b29eb5e83cf486a72c3dfa6f0ba5ee3b2b2e32 commit 214 166 684
f05893bf834b85795e8e592bbe51130bc1792ce1 commit 252 192 850
dbd772576def32be5be8aa55fdbc2e5143551d5d commit 157 117 1042
...
和執行 filter-branch
之前是一模一樣的。要不再 gc 一下看看?再次 gc 后,blob 文件又被打包,並生成了兩個新的 idx 文件和 pack 文件:
再次查看索引:
git verify-pack -v .git/objects/pack/pack-af08fc2fbea78dfae1503ae4b03578a4113da969.idx
——————
690898ddaf1b386531b9c1c81eec09eb18f4efa8 commit 208 166 12
fa322194e3401dfd14591d82951a9aca3b620631 commit 215 169 178
2c9790dbda1a83f64c7c7a8660f11aea87e14e78 commit 215 168 347
c4111b93fc90f70885dc71eae6be44811658197c commit 209 163 515
bb57bc14d163c5fc7b9bbce7f6e006a8e5c37564 commit 218 172 678
0c5e8cf09a2574a38e26c1c7cb0361f8e3b7f2b1 commit 218 175 850
50b29eb5e83cf486a72c3dfa6f0ba5ee3b2b2e32 commit 214 166 1025
2c4d78cfe2251613b1f23693a3ebe6f4ee9e793d commit 214 166 1191
...
690898
這個 commit 對象是最初添加大文件時的 commit,可以看到該 commit 對象依然存在在最新的 idx 文件中。實錘了,一番操作實際上只是工作目錄看起來空曠了,git 倉庫里不該有的文件還是一樣沒落下。
移除無用文件
執行到這一步目標很清楚,就是把 690898
這類已經不可達的 commit 和 pack-af08fc2fbea78dfae1503ae4b03578a4113da969.pack
這些已經沒有任何歷史提交引用的文件都刪除掉。
谷歌一搜,網絡上流傳的方法試一下:
rm -Rf .git/refs/original
rm -Rf .git/logs
git gc
可以看到,執行到這里,git/refs/original
里空空如也,並不需要刪什么東西,而且 git gc 我們剛也試過了,並沒有什么卵用。git prune
也是一樣的,因為 gc 實際上就是調用 git prune
。
事已至此,不方用 Java 垃圾回收的思想來理解:文件之所以沒有被刪掉,肯定是哪里還存在這引用,找出引用應該是解決問題的關鍵。
引入的大文件,從上面可以看出就是 04ddd8
這個,使用命令查看以下文件路徑
git rev-list --objects --all |grep 04ddd8
————
04ddd80c36932757f15327b35f5bcc123082c454 bigfile.zip
再隨便翻翻 git 本地目錄,查看 packed-refs
文件,refs
= references
,感覺有點東西啊,打開一看果然發現文本里記錄着 refs/original 相關的東西。
# pack-refs with: peeled fully-peeled sorted
fa322194e3401dfd14591d82951a9aca3b620631 refs/heads/master
bb57bc14d163c5fc7b9bbce7f6e006a8e5c37564 refs/heads/test
690898ddaf1b386531b9c1c81eec09eb18f4efa8 refs/original/refs/heads/master
c4111b93fc90f70885dc71eae6be44811658197c refs/original/refs/heads/test
690898
這個 SHA1 值很眼熟了,就是最開始的一個 commitID。再回看最初的 git 圖表,可以看到 690898
和 c4111b
分別指向操作前的 master 分支和 test 分支。
filter-branch
后,整個 git commit 樹都變了,大清都亡了,之前的這兩個引用肯定是沒用的舊引用了,刪刪刪!然后再使用命令檢查可達性 :
git fsck --full --unreachable
:驗證數據庫中對象的連通性和有效性。
Checking object directories: 100% (256/256), done.
Checking objects: 100% (37/37), done.
unreachable blob 04ddd80c36932757f15327b35f5bcc123082c454
unreachable commit 0c5e8cf09a2574a38e26c1c7cb0361f8e3b7f2b1
unreachable blob ab7268ccda89f9f58e6ae60050b360301cc91a71
unreachable commit 2c9790dbda1a83f64c7c7a8660f11aea87e14e78
unreachable tree 2fc5bb169f78176029674d4b5234d51e287df50f
unreachable commit c4111b93fc90f70885dc71eae6be44811658197c
unreachable tree 45a12cd512c7ae28d05b7b391e8d872031f1a89c
unreachable tree 4ed47074261cd7cb9f26b38452b56f88014ff2ac
unreachable commit 50b29eb5e83cf486a72c3dfa6f0ba5ee3b2b2e32
unreachable tree 5207eed7e90f90d9c6b6bf63cfea997033136def
unreachable tree 53b3421a3db498b5b15e96141a46e3c0b2431881
unreachable tree 598e2a6bb6dec5bb9b62c0bd757b66504413d5a2
unreachable commit dbd772576def32be5be8aa55fdbc2e5143551d5d
unreachable tree e27ea026bb5098ab33079172ae7835b27e40f4c4
unreachable tree 651d867dfc1f337090bf8bec6fef4459b369ac1a
unreachable commit 690898ddaf1b386531b9c1c81eec09eb18f4efa8
unreachable tree ed9833e93207376f729c517a5780aa7063bca0bb
unreachable commit f05893bf834b85795e8e592bbe51130bc1792ce1
unreachable tree 708923165513ea1259dcaff627b201b142545c6d
可以看到我們最想刪掉的大文件 04ddd8
被列出來,並且是不可達的。
再執行命令 git repack -A -d
,確保不可達的對象被解壓並保持解壓。然后再調用 git prune
重新計算項目大小:
count: 0
size: 0
in-pack: 18
packs: 1
size-pack: 2
prune-packable: 0
garbage: 0
size-garbage: 0
可以看到,無用的舊文件已經被清除了~
相關命令
這里記錄以下調研過程中遇到的命令,git 接觸很久了,但是很多命令卻還是第一次見,深感自己之渺小。
保存鏡像
git clone --mirror xxx地址
查看倉庫大小
git count-objects -v