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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

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

当前回答

这不再那么复杂,您只需在repo的克隆上使用gitfilter branch命令,即可选择不需要的子目录,然后推送到新的远程。

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

其他回答

编辑:添加了Bash脚本。

这里给出的答案对我来说只是部分奏效;缓存中仍有大量大文件。什么最终奏效了(在freenode上的#git中的几个小时后):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

在以前的解决方案中,存储库大小约为100 MB。这一次将其降至1.7 MB。也许这对某人有帮助:)


以下bash脚本自动执行任务:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

为了补充Paul的答案,我发现为了最终恢复空间,我必须将HEAD推到一个干净的存储库中,这样可以缩小.git/objects/pack目录的大小。

i.e.

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare

在gc修剪之后,还要执行以下操作:

$ git push ...ABC.git HEAD

那你就可以了

$ git clone ...ABC.git

并且减小了ABC/.git的大小

实际上,推送清理存储库不需要一些耗时的步骤(例如gitgc),例如:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD

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

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

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

更新:git子树模块非常有用,以至于git团队将其拉入核心并使其成为git子树。请参阅此处:将子目录分离(移动)到单独的Git存储库中

git子树可能对此有用

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt(已弃用)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

原始问题希望XYZ/ABC/(*文件)变为ABC/ABC/“*文件”。在为我自己的代码实现了公认的答案后,我注意到它实际上将XYZ/ABC/(*文件)更改为ABC/(*)文件。过滤器分支手册页甚至说,

结果将包含该目录(并且仅包含该目录)作为其项目根目录。"

换句话说,它将顶级文件夹“提升”一个级别。这是一个重要的区别,因为例如,在我的历史中,我重命名了一个顶级文件夹。通过将文件夹“提升”一级,git在我进行重命名的提交时失去了连续性。

我对这个问题的回答是制作存储库的两个副本,然后手动删除每个副本中要保留的文件夹。手册页支持我:

[…]如果一次简单的提交就足以解决您的问题,请避免使用[此命令]