我有一个Git存储库,其中包含许多子目录。现在我发现其中一个子目录与另一个子目录无关,应该分离到一个单独的存储库中。

如何在保留子目录中文件的历史记录的同时执行此操作?

我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在检查旧版本等时提供完整的树。这可能是可以接受的,但我更希望能够假装这两个存储库没有共享的历史。

为了清楚起见,我有以下结构:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

当前回答

我找到了非常直接的解决方案,这个想法是复制存储库,然后删除不必要的部分。这是它的工作原理:

1) 克隆要拆分的存储库

git clone git@git.thehost.io:testrepo/test.git

2) 移动到git文件夹

cd test/

2) 删除不必要的文件夹并提交

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) 使用BFG从历史记录中删除不必要的文件夹

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

对于多个文件夹,可以使用逗号java-jar bfg.jar--删除文件夹“{ABC1,ABC2}”metric.git

4) 检查历史记录是否不包含您刚刚删除的文件/文件夹

git log --diff-filter=D --summary | grep delete

5) 现在您有了没有ABC的干净存储库,所以把它推到新的原点

remote add origin git@github.com:username/new_repo
git push -u origin master

就是这样。您可以重复这些步骤来获取另一个存储库,

只需在步骤3中删除XY1、XY2并重命名XYZ->ABC

其他回答

更新:这个过程非常常见,git团队用一个新工具git子树简化了这个过程。请参阅此处:将子目录分离(移动)到单独的Git存储库中


您希望克隆存储库,然后使用gitfilter分支标记除要在新存储库中进行垃圾收集的子目录之外的所有内容。

要克隆本地存储库,请执行以下操作:gitclone/XYZ/ABC(注意:将使用硬链接克隆存储库,但这不是问题,因为硬链接文件本身不会被修改-将创建新的文件。)现在,让我们保留我们想要重写的有趣分支,然后删除源,以避免推到那里,并确保源不会引用旧的提交:cd/ABC对于分支1 br2 br3中的i;do git branch-t$i原点/$i;完成git远程rm源或对于所有远程分支:cd/ABC对于$中的i(git branch-r|sed“s/.*origin\///”);do git branch-t$i原点/$i;完成git远程rm源现在,您可能还想删除与子项目无关的标记;你也可以稍后再做,但你可能需要再次修剪你的repo。我没有这样做,并得到一个警告:Ref‘refs/tags/v0.1‘对于所有标签都是不变的(因为它们都与子项目无关);此外,在移除这些标签之后,将回收更多的空间。显然,gitfilter分支应该能够重写其他标记,但我无法验证这一点。如果要删除所有标记,请使用git-tag-l|xargs-git-tag-d。然后使用过滤器分支和重置来排除其他文件,以便可以对它们进行修剪。还让我们添加--tag-namefilter-cat--pruneempty以删除空提交并重写标记(注意,这将不得不去掉它们的签名):gitfilter branch--标记名filter cat--修剪空--子目录筛选器ABC----全部或者替代地,只重写HEAD分支并忽略标记和其他分支:gitfilter branch--标记名filter cat--修剪空--子目录筛选器ABC HEAD然后删除备份回流,以便真正回收空间(尽管现在操作是破坏性的)git重置--硬git for each ref--format=“%(refname)”refs/original/| xargs-n 1 git update ref-dgit reflog expire--expire=现在--全部git-gc--aggressive--prune=现在现在您有了ABC子目录的本地git存储库,并保留了其所有历史记录。

注意:对于大多数用途,gitfilter分支确实应该添加参数--all。是的,那真的是——空间——全部。这需要是命令的最后一个参数。正如Matli所发现的,这会将项目分支和标记保留在新的回购中。

编辑:以下评论中的各种建议都被纳入其中,以确保存储库实际上已经缩小(以前并非如此)。

Paul的回答创建了一个包含/ABC的新存储库,但没有从/XYZ中删除/ABC。以下命令将从/XYZ中删除/ABC:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

当然,首先在“clone--no hardlinks”存储库中测试它,然后使用Paul列出的reset、gc和prune命令进行测试。

您可以轻松尝试https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

这对我有用。我在上面给出的步骤中遇到的问题是

在此命令中,gitfilter branch--prune empty--子目录筛选器FOLDER-NAME branch-NAMEBRANCH-NAME是主如果由于保护问题提交时最后一步失败,请遵循以下步骤:https://docs.gitlab.com/ee/user/project/protected_branches.html

我推荐GitHub将子文件夹拆分为新存储库的指南。步骤与保罗的答案相似,但我发现他们的指示更容易理解。

我已经修改了说明,使其适用于本地存储库,而不是托管在GitHub上的存储库。


将子文件夹拆分到新存储库中打开Git Bash。将当前工作目录更改为要创建新存储库的位置。克隆包含子文件夹的存储库。git克隆OLD-REPOSTORY-FOLDER NEW-RPOSITORY-FOLDER将当前工作目录更改为克隆的存储库。cd REPOSTORY-NAME(维修人员姓名)要从存储库中的其余文件中筛选出子文件夹,请运行gitfilter branch,提供以下信息:FOLDER-NAME:项目中要从中创建单独存储库的文件夹。提示:Windows用户应使用/分隔文件夹。BRANCH-NAME:当前项目的默认分支,例如master或gh页面。gitfilter branch—修剪空—子目录筛选器FOLDER-NAME branch-NAME#筛选目录中的指定分支并删除空提交重写48dc599c80e20527ed902928085e7861e6b3cbe6(89/89)Ref“refs/heads/BRANCH-NAME”被重写

在垃圾收集之前,您可能需要像“gitreflog-expire--expire=now--all”这样的内容来实际清理文件。gitfilter分支只删除历史记录中的引用,但不删除保存数据的reflog条目。当然,先测试一下。

尽管我的初始条件有所不同,但我的磁盘使用量在这样做时大幅下降。也许--子目录过滤器否定了这种需要,但我对此表示怀疑。