Git 仓库拆分

方案对比

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 仓库,这样数据量小,排查起来会比较方便。

模拟git仓库

看一下 objects 文件夹:

本地git目录

在操作之前,先看一下仓库大小,使用 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格式的,存放在这个目录中。

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 图表变成:

git 图表

可以看到,与 aaa 文件夹下文件无关的 commit 都被移除了。分支也都被保留了,棒棒的!正是我们想要的效果。再看保留下来的各个 commit 的内容:

操作后的 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本地目录

虽然工作目录中不需要的文件已经被清除了,但是 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 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 本地路径

可以看到,执行到这里,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 图表,可以看到 690898c4111b 分别指向操作前的 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

git 本地目录

重新计算项目大小:

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

posted @ 2020-09-26 22:59  扬州慢  阅读(1328)  评论(0编辑  收藏  举报