对 Git 仓库进行瘦身

2,968

Git 仓库随着 commit 次数的增加变得越来越大,尤其是当 push 了大文件之后,即使后面把该文件删除了,也会被 Git 记录下来,以便能够随时回滚到指定提交。

以笔者的某个 Git 仓库为例,整个目录有 3GB,其中 .git 目录本身就占了 2.7GB,Git 仓库过大的话会有如下问题:

  • 占用电脑的存储空间
  • 别人 clone 耗时太长
  • 切换分支占用内存

如果之前提交的大文件后面不再使用了,可以考虑对仓库进行一次「瘦身」。先说结果吧,瘦身后的仓库只有 80M,整整优化了 97% 的体积!那是如何操作的呢?

这里推荐一个工具叫 BFG, 可以快速帮助我们清理 Git 仓库,非常令人放心的是,该工具不会删除最后一次提交时仓库里面的任何文件,只是对历史记录进行清理。话不多说,直接看操作步骤:

第一步:安装 BFG 命令行工具

BFG 依赖 Java 环境,所以确保电脑上已经安装了 Java。然后去官网下载 jar 包,例如 bfg-1.14.0.jar。然后在命令行创建别名:

alias bfg='java -jar /xxxx/bfg-1.14.0.jar'

如果是 Mac 的话,可以直接用 brew 来安装:

brew install bfg

第二步:下载镜像仓库

命令是:

git clone --mirror git://example.com/your-repo.git

git clone 命令实现版本库克隆,主要有如下三种用法:

  1. git clone <repository> <directory>
  2. git clone --bare <repository> <directory.git>
  3. git clone --mirror <repository> <directory.git>

这里采用第三种,即带 --mirror 参数的。你可能会问,这三种有什么区别呢?

  • 用法1将 <repository> 指向的版本库创建一个克隆到 <directory> 目录。目录 <directory> 相当于克隆版本库的工作区,文件都会检出,版本库位于工作区下的 .git 目录中。
  • 用法2和用法3创建的克隆版本库都不含工作区,直接就是版本库的内容,这样的版本库称为裸版本库。一般约定俗成裸版本库的目录名以.git为后缀,所以上面示例中将克隆出来的裸版本库目录名写做 <directory.git>
  • 用法3区别于用法2之处在于用法3克隆出来的裸版本对上游版本库进行了注册,这样可以在裸版本库中使用 git fetch 命令和上游版本库进行持续同步。

如果仓库较大,建议下载完了之后,先用下面的命令备份一下,方便等会操作失误重做。

cp -r your-repo.git your-repo-backup.git

第三步:执行清理命令

根据需要,运行 bfg 命令,例如清理所有超过 10M 的文件:

bfg --strip-blobs-bigger-than 10M your-repo.git

清理前 100 大的文件:

bfg --strip-biggest-blobs 100 your-repo.git

指定文件或按照通配符清理文件:

bfg --delete-files "node.exe" your-repo.git
bfg --delete-files "*.zip" your-repo.git

保护指定分支(默认是master),这个命令非常有用,表示哪些分支的最新提交后的文件不要删除:

bfg --protect-blobs-from master,feat/latest --delete-files "*.zip" your-repo.git
bfg --protect-blobs-from master,dev,stage --strip-biggest-blobs 100 your-repo.git

当然,如果你不想在任何分支中看到这个文件的话,可以这么写:

bfg --delete-files "*.zip" --no-blob-protection your-repo.git

bfg 非常棒的地方是,每次命令都会生成日志,放在跟 your-repo.git 仓库平级的 your-repo.git.bfg-report 目录下,有个 deleted-files.txt 可以清晰的看到哪些文件被删除了,例如:

d2c18a31fef1d60f47 8316352 dog.png
e00f5efc61fc18a37c 8205751 cat.png
c8c27e0758df6d9795 9878146 wix.exe

第四步:更新 Git 历史

确定第三步想要清理的命令都执行完了,运行下面的命令对仓库历史进行改写,运行速度会比较慢:

cd your-repo.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive

第五步:提交到远程仓库

到目前为止,所有的操作还只是本地的,需要把瘦身过的仓库提交到远程:

cd your-repo.git
git push --mirror

这个时候,仓库的历史才被真正改写了,重新 clone 之后,将获得瘦身后的仓库。

不过在多人协作的时候,你要告诉所有协作者删除本地旧仓库,重新 clone 新仓库,如果某些小伙伴一不小心又把本地旧仓库 push 上去,你的工作都白做了...

所以为了彻底防止上述情况发生,也可以重新建一个新仓库,用下面的命令改写 origin 地址,push 到新仓库,通知所有人下载这个新仓库即可:

cd your-repo.git
git remote set-url origin your-new-repo.git
git push --mirror