en title: Git Splitting a Subfolder Out Into a New Repository

需求

某个历史悠久的“大”Git仓库(很大很大。。。)要拆分成很多小仓库。

至于为什么会有这种“大”仓库的存在,表问我。。。 it’s about project history

为什么要这样做?

  • 查bug的时候不再需要同时切换新旧两个仓库交叉查看历史commit记录
  • 可以保留被拆分目录下文件的完整提交记录,方便他人阅读代码和理解代码

这样做会不会增加迁移的负担?

不会,绝对不会。一条命令即可搞定 (git filter-repo xxxxx)。

准备工作

git-filter-repo 现在是Git官方推荐的工具(git自带的filter-branch已经不被推荐使用了,速度慢又不好用)。

git filter-repo is now recommended by the git project instead of git filter-branch. https://git-scm.com/docs/git-filter-branch#_warning

注: github 官方文档里也有个拆分子目录的教程,那个是用的filter-branch, 已经过时了,不用看了。

安装filter-repo

安装git-filter-repo 只需要执行一次:

pip3的模块带的可执行文件默认情况下会安装到 $HOME/.local/bin, 因此要把$HOME/.local/bin加入PATH 环境变量。

如果你的系统有不同的配置,以你自己的系统为准,比如Mac之类的, 如果偏爱brew, 可以参考官方文档 https://github.com/newren/git-filter-repo/blob/main/INSTALL.md

#安装 filter-repo
pip3 install --user git-filter-repo

# 将以下环境变量配置放到你自己正在使用的shell的rc配置文件中(如.bashrc, .zshrc 等)
export PATH="$HOME/.local/bin:$PATH"

大仓库准备(只需要执行一次):

大仓库作为历史仓库,需要拆分成很多小仓库,原则上我们不会再添加新代码了。

首先,我们将大仓库THE_BIG_REPO clone一份,保存为THE_BIG_REPO.orig, 避免以后每次要从github clone,速度太慢。

这里 ~/repo/go/split-work 是我用于迁移专门准备的目录。

    ~/repo/go/split-work
❯ cd ~/repo/go/split-work
❯ git clone [email protected]:VendorName/THE_BIG_REPO.git THE_BIG_REPO.orig

THE_BIG_REPO.orig 作为拆分的原始仓库, 其它拆分每次从这个目录clone


迁移步骤

1.拆分子目录为仓库

以 xxx_write_agent 这个子目录(实际上它是一个单独的微服务)为例。

~/repo/go/split-work 是我用于迁移专门准备的目录。

cd ~/repo/go/split-work/

# 将我们之前clone好的大仓库,clone一份到当前目录,为方便识别,重命名目录为 xxx_write_agent
git clone ~/repo/go/split-work/THE_BIG_REPO.orig xxx_write_agent

# 进入  xxx_write_agent
cd xxx_write_agent/
 
# 20200723 修正:git checkout xxx 不能执行,执行后会提示 Refusing to overwrite repo history since this does not look like a fresh clone.

# 好了,关键的操作来了
# 现在我们所在的 xxx_write_agent 目录,实际上是整个老仓库
# 假设我们要迁移的 xxx_write_agent 目录相对于当前目录的路径是:  a-parent-path/project/another-pkg/xxx_write_agent
# 执行以下命令,将自动干掉其它目录,只保留 xxx_write_agent 目录及其commit记录
git filter-repo --subdirectory-filter a-parent-path/project/another-pkg/xxx_write_agent

好了,现在的 xxx_write_agent 仓库已经是名副其实的 xxx_write_agent 了。

接下来我们要做一些清理工作


2.清理工作

清理除 dev 之外的其它branch

由于在老仓库,我们的 dev 是最新的分支,因此,我们只需要保留 dev 分支即可。其它branch和tags 如果推送到了新仓库,会带来干扰。

值得高兴的是, 之前的仓库没有发过任何版本,也不存在任何tag, 因此我们可以省掉清tag这一步了。只需要清除branch。

如果你的情况不同,请按你自己的来,比如,你的仓库很可能是 master 是最新的。

以下命令中git br 是一个git别名, 有人可能配置成了shell的别名,比如gbr, 如果没有的,请自行把它换成原版的完整命令 git branch

git checkout dev && git br | grep -v '*' | xargs -I'{}' git br -D '{}'

将原dev(最新分支)重命名为 develop (主要是用于git flow)

git br -m dev develop

在dev的基础上创建 master 分支 (主要是用于git flow, 初始化时必须有master分支):

git br master

初始化 git flow:

(注, 我这里用了默认配置,这个是我在全局.gitconfig中已经配置好了,请参见末尾"git flow 相关配置”)

❯ git flow init -d 
Using default branch names.

Which branch should be used for bringing forth production releases?
   - develop
   - master
Branch name for production releases: [master] 

Which branch should be used for integration of the "next release"?
   - develop
Branch name for "next release" development: [develop] 
Hooks and filters directory? [/home/ttys3/repo/go/split-work/split/THE_BIG_REPO/.git/hooks]

给原始代码新建一 0.0.1 版本,记录一下这是原始迁移过来的代码。后续的发版都是从 0.1.0 开始,因此可以很好的区分哪些是历史commit.

git flow release start 0.0.1 && git flow release finish '0.0.1'

message内容为:

split subdir to repo from github.com/VendorName/THE_BIG_REPO dev branch

好了,到这里,拆分并保留commit记录的任务已经完成了。

其实核心的命令只有一条:

git filter-repo --subdirectory-filter 需要保留的子目录的相对路径

git flow 相关配置

编辑全局.gitconfig 配置文件,增加git flow 相关配置:

[gitflow "prefix"]
    feature = feature/
    bugfix = bugfix/
    release = release/
    hotfix = hotfix/
    support = support/
    versiontag = v