将子目录分离(移动)到单独的Git仓库

将子目录分离(移动)到单独的Git仓库

技术背景

在软件开发过程中,随着项目的不断发展,可能会发现某个子目录下的代码有独立维护和管理的需求,例如该子目录下的代码可以作为一个独立的组件被多个项目复用,或者为了更好地组织代码结构。这时就需要将该子目录从原有的大仓库中分离出来,成为一个独立的Git仓库。

实现步骤

简单方法(Git 1.7.11及以上版本)

  1. 准备旧仓库
    1
    2
    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    注意
    • <name-of-folder> 不能包含前导或尾随字符,例如名为 subproject 的文件夹必须写成 subproject,而不是 ./subproject/
    • <name-of-new-branch> 是在现有/旧仓库中创建的分支,而不是后面要创建的新仓库的分支。
    • Windows 用户:当文件夹深度大于 1 时,<name-of-folder> 必须使用 *nix 风格的文件夹分隔符 /,例如 path1\path2\subproject 必须写成 path1/path2/subproject
  2. 创建新仓库
    1
    2
    3
    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
  3. 将新仓库链接到 GitHub 或其他远程仓库
    1
    2
    git remote add origin <[email protected]:user/new-repo.git>
    git push -u origin master
  4. 清理旧仓库(可选)
    1
    git rm -rf <name-of-folder>

多子文件夹分离

  1. 准备旧仓库
    1
    2
    3
    4
    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    注意
    • <name-of-folder> 不能包含前导或尾随字符。
    • Windows 用户:当文件夹深度大于 1 时,<name-of-folder> 必须使用 *nix 风格的文件夹分隔符 /,并且不要使用 mv 命令,而是使用 move
  2. 创建新仓库
    1
    2
    3
    4
    mkdir <new-repo>
    pushd <new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
  3. 将新仓库链接到 GitHub 或其他远程仓库
    1
    2
    git remote add origin <[email protected]:my-user/new-repo.git>
    git push origin -u master
  4. 清理(可选)
    1
    2
    3
    popd # 退出 <new-repo>
    pushd <big-repo>
    git rm -rf <name-of-folder>

使用 git filter-branch

  1. 克隆本地仓库
    1
    git clone /XYZ /ABC
  2. 保留要重写的分支并移除 origin
    1
    2
    3
    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
  3. 移除与子项目无关的标签(可选)
    1
    git tag -l | xargs git tag -d
  4. 使用 filter-branch 排除其他文件
    1
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
  5. 删除备份引用日志以释放空间
    1
    2
    3
    4
    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now

使用 git filter-repo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在目录 XYZ 中创建原始仓库的本地克隆
tmp $ git clone [email protected]:user/original.git XYZ

# 切换到 XYZ 目录工作
tmp $ cd XYZ

# 保留子目录 XY1 和 XY2(删除 ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# 注意:原始远程 origin 已被删除
# (防止意外推送覆盖原始仓库数据)

# 指向新的托管专用仓库
XYZ $ git remote add origin [email protected]:user/XYZ.git

# 推送(并跟踪)远程 master 分支
XYZ $ git push -u origin master

核心代码

使用 git subtree 分离子目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 准备旧仓库
cd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>

# 创建新仓库
mkdir ~/<new-repo> && cd ~/<new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>

# 链接到远程仓库
git remote add origin <[email protected]:user/new-repo.git>
git push -u origin master

# 清理旧仓库(可选)
cd <big-repo>
git rm -rf <name-of-folder>

使用 git filter-branch 分离子目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 克隆仓库
git clone /XYZ /ABC
cd /ABC

# 处理分支和标签
for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
git remote rm origin
git tag -l | xargs git tag -d

# 过滤分支
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all

# 清理空间
git reset --hard
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git reflog expire --expire=now --all
git gc --aggressive --prune=now

使用 git filter-repo 分离子目录

1
2
3
4
5
git clone [email protected]:user/original.git XYZ
cd XYZ
git filter-repo --path XY1 --path XY2
git remote add origin [email protected]:user/XYZ.git
git push -u origin master

最佳实践

  • 在进行分离操作之前,建议先在测试环境中进行尝试,确保操作不会导致数据丢失或损坏。
  • 如果需要保留完整的历史记录,尤其是在子目录有重命名操作的情况下,可以采用复制仓库并手动删除不需要的文件的方法。
  • 定期清理仓库中的无用数据,如使用 git reflog expiregit gc 命令,以减小仓库的大小。

常见问题

合并提交未被移除

使用 filter-branch 时,合并提交可能不会被移除。这通常是一个外观问题,如果不影响实际使用,可以忽略。

提交重复

使用 filter-branch 后可能会出现提交重复的问题,这可能是由于删除了某个合并提交导致的。可以尝试手动解决或重新进行操作。

操作缓慢

对于大型仓库,使用 git filter-branch 可能会非常缓慢。可以考虑使用基于 libgit2 的 git_filter 工具,它可以显著提高处理速度。

推送失败

如果在推送时遇到保护分支的问题,可以参考相关文档(如 GitLab 的受保护分支文档)进行解决。


将子目录分离(移动)到单独的Git仓库
https://119291.xyz/posts/detach-subdirectory-into-separate-git-repository/
作者
ww
发布于
2025年5月23日
许可协议