将许多子目录分离到一个新的,单独的Git存储库中

时间:2010-06-05 20:51:53

标签: git git-filter-branch

此问题基于Detach subdirectory into separate Git repository

我想分离一对子,而不是分离一个子目录。例如,我当前的目录树如下所示:

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

而我想这样做:

/apps
  /AAA
/libs
  /XXX

--subdirectory-filter的{​​{1}}参数将不起作用,因为它在第一次运行时除了给定目录之外的所有内容。我认为使用git filter-branch参数可以使用所有不需要的文件(尽管很乏味),但是如果我尝试不止一次运行它,我会收到以下消息:

--index-filter

有什么想法吗? TIA

10 个答案:

答案 0 :(得分:124)

不必处理子shell并使用ext glob(如kynan所建议的那样),尝试这种更简单的方法:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

答案 1 :(得分:32)

使用简单git命令的手动步骤

计划是将各个目录拆分为自己的存储库,然后将它们合并在一起。以下手动步骤不使用极客使用的脚本,而是易于理解的命令,可以帮助将额外的N个子文件夹合并到另一个单独的存储库中。

<强>鸿沟

让我们假设你的原始回购是: original_repo

1 - 拆分应用:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2 - 拆分库

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

如果您有两个以上的文件夹,请继续。现在你将有两个新的和临时的git存储库。

通过合并应用和库来

征服

3 - 准备全新的回购:

mkdir my-desired-repo
cd my-desired-repo
git init

您需要至少进行一次提交。如果应该跳过以下三行,您的第一个回购将立即出现在您的回购根下:

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

提交临时文件后,后面部分中的merge命令将按预期停止。

根据用户的反馈,您可以选择添加a_file_and_make_a_commit.gitignore等,而不是添加README.md等随机文件。

4 - 首先合并应用程序repo:

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

现在,您应该在新存储库中看到 apps 目录。 git log应显示所有相关的历史提交消息。

注意:正如Chris在评论中所述,对于git的较新版本(&gt; = 2.9),您需要使用--allow-unrelated-histories指定git merge

5 - 以相同的方式合并libs repo:

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

如果您有超过2个repos要合并,请继续。

参考:Merge a subdirectory of another repository with git

答案 2 :(得分:26)

为什么要多次运行filter-branch?你可以在一次扫描中完成所有操作,所以不需要强制它(请注意你需要在shell中启用extglob才能使其工作):

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

这应该消除不需要的子目录中的所有更改并保留所有分支和提交(除非它们仅凭--prune-empty影响已修剪子目录中的文件) - 没有重复提交等问题。

执行此操作后,不需要的目录将被git status列为未跟踪。

$(ls ...)是必要的。 extglob由您的shell而非索引过滤器评估,该过滤器使用sh内置eval(其中extglob不可用)。有关详细信息,请参阅How do I enable shell options in git?

答案 3 :(得分:19)

在这里回答我自己的问题......经过大量的反复试验。

我设法使用git subtreegit-stitch-repo的组合来完成此操作。这些说明基于:

首先,我将要保留的目录拉出到他们自己的独立存储库中:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

然后我创建了一个新的空存储库,并将最后两个导入/拼接到其中:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

这会创建两个分支master-Amaster-B,每个分支都包含其中一个拼接回购的内容。将它们组合起来并进行清理:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

现在我不太确定如何/何时发生这种情况,但在第一个checkoutpull之后,代码会神奇地合并到主分支中(任何关于这里发生的事情的见解是理解!)

所有内容似乎都按预期工作,但如果我查看newRepo提交历史记录,则当变更集同时影响apps/AAAlibs/XXX时,会有重复项。如果有办法删除重复项,那么它将是完美的。

答案 4 :(得分:15)

一个简单的解决方案:git-filter-repo

我遇到了类似的问题,在回顾了这里列出的各种方法之后,我发现了git-filter-repo。在官方git文档here中,建议使用它作为git-filter-branch的替代方法。

要从现有存储库中的一部分目录创建新存储库,可以使用以下命令:

git filter-repo --path <file_to_keep>

通过链接来过滤多个文件/文件夹:

git filter-repo --path keepthisfile --path keepthisfolder/

因此,要回答原始问题,使用git-filter-repo您只需要以下命令:

git filter-repo --path apps/AAA/ --path libs/XXX/

答案 5 :(得分:7)

我已经编写了一个git过滤器来解决这个问题。 它有着名的git_filter,位于github:

https://github.com/slobobaby/git_filter

它基于优秀的libgit2。

我需要拆分一个包含很多提交的大型存储库(~100000),并且基于git filter-branch的解决方案需要几天才能运行。 git_filter花一点时间做同样的事情。

答案 6 :(得分:7)

使用&#39; git splits&#39; git扩展

git splits是一个bash脚本,它是git branch-filter的包装器,是基于jkeating's solution创建的git扩展名。

这完全是出于这种情况。对于您的错误,请尝试使用git splits -f选项强制删除备份。因为git splits在新分支上运行,所以它不会重写当前分支,因此备份是无关紧要的。有关更多详细信息,请参阅自述文件,请务必在回购的副本/克隆中使用它(以防万一!)

  1. 安装git splits
  2. 将目录拆分为本地分支 #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ apps/AAA libs/ZZZ

  3. 在某处创建一个空的仓库。我们假设我们在GitHub上创建了一个名为xyz的空仓库,其路径为:git@github.com:simpliwp/xyz.git

  4. 推送到新的回购。 #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. 将新创建的远程仓库克隆到新的本地目录中 #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git

答案 7 :(得分:3)

呀。在后续调用-f时使用filter-branch标志强制覆盖备份以覆盖该警告。 :)否则我认为你有解决方案(即用filter-branch一次消除不需要的目录。)

答案 8 :(得分:1)

git clone git@example.com:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
    branch=${originBranch:7:${#originBranch}}
    git checkout $branch
done
git checkout master

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all

git remote set-url origin git@example.com:newthing.git
git push --all

答案 9 :(得分:-4)

删除refs / original中.git目录下的备份,如消息所示。该目录已隐藏。