碰到了一个这样的场景:同事为了单元测试,将众多测试资源文件提交到了git仓库内,这导致仓库体积陡然膨胀。但另一方面,编写好的测试用例又确实依赖这些测试资源文件。那么,有没有办法能达到以下目标:

  • 分离工程文件和测试资源,以便能够单独管理二者
  • 清洗提交记录,将测试资源在提交历史中“抹去”,以便减小仓库的体积

搜索一番,发现了git submodulegit filter-repo两个工具刚好可以满足这两个需求。

1 git submodule的使用

submodule是git自带的一个工具,详细的介绍参见Git-工具-子模块,这里不再赘述,只简单说明如何利用git submodule满足上述需求。

1.1 从仓库中移除测试资源

假设工程结构如下:

├─MyLib
│  ├─include
│  └─src
├─MyLibTest
   ├─assets
   └─src

其中MyLib为库的工程,而MyLibTest则是MyLib的单元测试工程,测试资源存放在MyLibTest/assets下,也正是我们需要移除的目录。

首先,在git bash中执行

git rm -r --cached MyLibTest/assets

MyLibTest/assets从git仓库索引中移除,但不实际删除该目录。

关于git rm的使用,参见git-rm

1.2 新建测试资源仓库

新建远程仓库

在git服务器上新建一个仓库,记作MyLibTestResource,用于存放测试资源。

新建本地仓库

新建一个目录,这里记作TestResource,在其中建立git仓库,将MyLibTest/assets中的内容复制到该目录下,随后提交。再执行

git remote add origin git@MyLibTestResource.git #git@MyLibTestResource.git替换为MyLibTestResource的真实git地址

添加远程仓库,随后执行git push推送即可。

1.3 添加子模块

回到MyLib下,执行

git submodule add git@MyLibTestResource.git MyLibTest/assets

添加子模块MyLibTestResource,子模块的内容会同步到MyLibTest/assets下。

1.4 克隆包含子模块的项目

克隆包含子模块的项目有二种方法:一种是先克隆父项目,再更新子模块;另一种是直接递归克隆整个项目。

克隆父项目,再更新子模块

#克隆父项目MyLib
git clone git@MyLib.git #git@MyLib.git替换为MyLib的真实git地址

#初始化子模块
cd MyLib
git submodule init

#更新子模块
git submodule update

递归克隆整个项目

git clone git@MyLib.git --recursive

2 git filter-repo的使用

利用git submodule可以将测试资源从MyLib中分离,但MyLib仓库中仍然记录着测试资源的提交与更改,这导致仓库体积仍然很庞大。那么,如何改写git提交历史,将测试资源相关的提交与更改从历史中“抹去”呢?

对于重写历史,我们已有git commit --amendgit rebase等工具可用,但这些指令都只适用于一个或几个提交的修改,修改大量提交时用这些指令会显得非常繁琐。Git还提供了一个git filter-branch用于改写历史中的大量提交,不过很可惜,它至少有以下缺陷:

  • 清理速度慢
  • 只能按文件名清理

由于git filter-branch实现上的缺陷,第三方的重写历史工具应此而生,如BFG Repo Cleanergit filter-repo。这两个工具应该都可以满足我们的需求,不过由于git官方教程推荐了后者,故决定选取git filter-repo

2.1 git filter-repo的安装

2.1.1 Python和pip的安装

git filter-repo是用Python开发的工具,因此需要安装Python环境,其安装要求如下:

  • git >= 2.22.0 at a minimum; some features require git >= 2.24.0 or later
  • python3 >= 3.5

https://www.python.org/downloads/release/python-384/下载安装Python 3.8.4(当然,如果是Linux环境可以直接用包管理工具安装)

pip是Python的包管理器,我们随后将用pip安装filter-repo。从Python 3.4开始,官网的安装包中已经自带了pip,在安装时用户可以直接选择安装。也可以从https://pypi.org/project/pip/#files下载pip的源码包,解压后执行>

python setup.py install

完成pip的安装。

2.1.2 filter-repo的安装

命令行执行

pip3 install git-filter-repo

即可。

2.2 滤除目录

MyLib目录中,执行

git filter-repo --path MyLibTest/assets --invert-paths

即可滤除MyLibTest/assets在历史中的记录。其中--path用于指定目录,--invert-paths说明指定的目录需要从历史中滤除。

关于git filter-repo的详细使用,参见git-filter-repo Manual Page

2.3 重写推送仓库

在更改提交的历史后,需要重新推送仓库,执行

git push --force

即可。

注:使用--force选项覆盖提交需要你拥有对应的权限。并且一旦操作完成,本地和远程仓库的提交历史都将被改写,操作前请确认这样是否能被接受