Git如何将仓库按子文件夹拆分并保留所有旧分支?

31

我有一个包含2个目录和多个分支的 git 仓库,我想将其拆分并创建所有分支。

`-- Big-repo
    |-- dir1
    `-- dir2

Branches : branch1, branch2, branch3 ...

我想要的内容

我希望将dir1和dir2作为两个独立的仓库分离,并在这两个仓库中保留分支branch1、branch2等。

dir1
Branches : branch1, branch2, branch3 ...

dir2
Branches : branch1, branch2, branch3 ...

我的尝试:

我能够使用该方法将它们分成两个仓库。

git subtree split -P dir1 -b dir1-only 
git subtree split -P dir2 -b dir2-only 

但是,在分离后它没有创建任何分支。

要获取所有分支:

git checkout branch1 (in Big-repo)
git subtree split -p dir1 -b dir1-branch1

git checkout branch2 (in Big-repo)
git subtree split -p dir1 -b dir1-branch2

And push these branches to newly created repo.

这需要更多的手动操作,我相信可能有一种快速实现的方法?

有任何想法吗???

2个回答

53

简短回答

git filter-branch提供了您所需的功能。使用--subdirectory-filter选项,您可以创建一个新的提交集,其中subDirectory的内容位于目录的根目录下。

git filter-branch --prune-empty --subdirectory-filter subDirectory -- --branches

步骤

以下是以安全的方式执行此操作的示例。您需要为每个将被隔离到自己的存储库中的子目录执行此操作,在本例中为dir1

首先,克隆您的存储库以保持更改的隔离:

git clone yourRemote dir1Clone
cd dir1Clone
为了准备克隆的代码库,我们将重新创建所有远程分支作为本地分支。我们跳过以*开头的那个分支,因为那是当前的分支,在这种情况下会显示(no branch),因为我们处于无头状态:

为了准备克隆的代码库,我们将把所有远程分支重新创建为本地分支。我们跳过以*开头的分支,因为它是当前分支,在这种情况下会显示(no branch),因为我们处于无头状态:

# move to a headless state
# in order to delete all branches without issues
git checkout --detach

# delete all branches
git branch | grep --invert-match "*" | xargs git branch -D
为了在本地重新创建所有远程分支,我们通过执行 git branch --remotes 命令的结果进行操作。我们跳过包含 -> 的分支,因为它们不是实际的分支:

要在本地重新创建所有远程分支,我们需要查看 git branch --remotes 的输出结果。但我们会跳过其中包含->的内容,因为这些不是真正的分支:

# get all local branches for remote
git branch --remotes --no-color | grep --invert-match "\->" | while read remote; do
    git checkout --track "$remote"
done

# remove remote and remote branches
git remote remove origin

最后运行filter-branch命令。这将创建新的提交,其中包含所有涉及dir1子目录的提交。同时还会更新涉及该子目录的所有分支。输出将列出所有未更新的引用,对于根本没有涉及dir1的分支来说,就是这种情况。

# Isolate dir1 and recreate branches
# --prune-empty removes all commits that do not modify dir1
# -- --all updates all existing references, which is all existing branches
git filter-branch --prune-empty --subdirectory-filter dir1 -- --all

完成后,您将获得一组新的提交,其中根据库的根目录为dir1。只需添加您的远程分支以推送新的提交,或将其用作全新的存储库。

如果您关心存储库的大小,可以将以下步骤作为最后一步:

即使所有分支都已更新,您的存储库仍将保留原始存储库的所有对象,尽管只能通过引用日志访问。如果要删除这些内容,请阅读如何清除Git中无用的提交

一些其他资源:


2
谢谢LopSae.. 这对我帮助很大。这正是我正在寻找的东西。 - Sridhar
1
我尝试了你的方法,但标签没有被转移,最终我得到了一个包含标签的悬空分支。有没有办法将标签转移到过滤后的分支? - Uga Buga
1
使用 -- --all 选项应该迁移两个分支和标签。如果你只想要标签,那么 -- --tags 选项是存在的。请记住,如果您正在为未迁移的提交打标签(例如,如果它从未触及子目录中的文件),则会打印警告并将标签留在原地。 - Maic López Sáenz
@LopSae,我尝试使用“git subtree split”,但是我必须逐个分支进行操作,这太耗时了...有没有什么快捷方式/技巧可以在所有分支/标签上使用子树命令? - Sridhar
@Sridhar 我不知道。据我所知,split work 只适用于单个分支。 - Maic López Sáenz
感谢您抽出时间编写这个教程,非常有帮助。 - skwidbreth

10

这个脚本对我来说很有用:

#!/bin/bash

set -e

if [ -z "$3" ]; then
        echo "usage: $0 /full/path/to/repository path/to/splitfolder/from/repository/root new_origin"
        exit
fi

repoDir=$1
folder=$2
newOrigin=$3

cd $repoDir

git checkout --detach
git branch | grep --invert-match "*" | xargs git branch -D

for remote in `git branch --remotes | grep --invert-match "\->"`
do
        git checkout --track $remote
        git add -vA *
        git commit -vam "Changes from $remote" || true
done

git remote remove origin
git filter-branch --prune-empty --subdirectory-filter $folder -- --all

#prune old objects
rm -rf .git/refs/original/*
git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

#upload to new remote
git remote add origin $newOrigin
git push origin master

for branch in `git branch | grep -v '\*'`
do
        git push origin $branch
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接