从现有项目中拆分Git Subtree

使用Git Subtree来管理不同项目中的公共部分。这是平时非常常见的需求。

比如说一个node后端服务使用express框架,前端静态页面放在dist文件夹中。前端工程在另外一个git仓库中,编译生成的静态文件放到这个dist文件夹里面。我们把前端工程叫做P1项目,后端项目叫做P2项目,dist文件夹所在的git项目叫做S项目。 通常情况下都是P1和P2项目都已经存在了,发现这两个项目需要提取公共模块,于是第一步是要拆分现有的工程项目。

1. 拆分现有项目

前端项目P1中已经有了dist文件夹,我们不想更改文件夹的名字。那么将现有的文件夹当作subtree的路径。

cd P1_PATH
git subtree split -P <S项目的相对路径> -b <临时branch>

// 注: -P--prefix 等价

在这个例子中具体的命令如下,临时分支名叫做subtree

git subtree split -P dist -b subtree

2. 创建子repo

dist目录下新建一个git repo。由于现在dist目录下是有文件的,在pull之前的历史记录时会有问题,所以这个时候把现有的文件都删掉。

cd dist
rm -rf *
git init
git pull <P1项目路径> <临时branch>
git remote add origin <S项目的git仓库>
git push origin -u master

在这里具体的pull命令如下

git pull ../ subtree

3. 清理数据

在原项目中清除dist下的文件。清理步骤如下:

cd P1_PATH
git rm -rf <S项目相对路径>
git commit	#提交删除记录
git branch -D <临时branch> #删除临时分支

本例中具体命令如下:

cd ..
git rm -rf dist
git commit -m '删除dist'
git branch -D subtree
rm -rf dist

在这里把dist物理文件都删除没有关系。

4. 添加subtree

接下来将subtree仓库地址链接到当前项目,命令如下:

git subtree add --prefix=<S项目相对路径> <S项目git地址> <分支> --squash

S项目目前只有一个master分支,所以现在具体的命令如下:

git subtree add --prefix=dist S_GIT_REMOTE_PATH master --squash

--squash参数表示将subtree repo中的提交记录合并成一次commit,这样就不用拉取子项目的完整历史记录。

然后我们用git push把当前改动提交到远程就可以了。

5. 提交与更新subtree

完成subtree的分离和添加以后我们可以正常的改动代码和提交。这些改动可能涉及到S项目的改动,这些都没有关系。等到我们需要同步S项目的时候,例如前端完成了一个新版本的开发,编译出一份静态资源,需要同步到后端测试。这时候用git subtree push命令来提交S项目。

git subtree push --prefix=<S项目相对路径> <S项目git地址> <分支>

Git会遍历之前的commit,找到对S目录的更改,然后把这些更改记录提交到S的Git仓库中。在本例中具体命令如下:

git subtree push --prefix=dist S_GIT_REMOTE_PATH master

在后端的P2项目中使用git subtree pull --prefix=<S项目相对路径> <S项目git地址> <分支>来更新

git subtree pull --prefix=dist S_GIT_REMOTE_PATH master

当然在P2项目中重复上述步骤添加subtree,之后就可以愉快的使用git subtree来使用公共模块了。 在本例中我们是分离一个现有的目录当作subtree,如果是新建一个目录来来当作subtree就会稍微简单一点。修改一下步骤2就可以了。

需要注意的是,并不是任意的git仓库都可以添加为subtree的,因为subtree的commit会被合并到当前项目历史记录中,所以如果两个仓库的历史记录无关的话是无法合并的。所以正确的姿势就应该是从当前项目中split出一个子项目来当作subtree。