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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

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

当前回答

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

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

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

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

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

其他回答

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

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

这里的大多数答案似乎都依赖于某种形式的gitfilter分支——子目录筛选器及其类似的分支。这可能在“大多数情况下”有效,但在某些情况下,例如重命名文件夹时,例如:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

如果您使用普通的git过滤器样式来提取“move_this_dir重命名”,则会丢失最初为“move_this_dir”(ref)时发生的文件更改历史记录。

因此,似乎真正保留所有更改历史的唯一方法(如果您的情况是这样的),本质上就是复制存储库(创建一个新的repo,将其设置为原点),然后对所有其他内容进行核处理,并将子目录重命名为父目录,如下所示:

在本地克隆多模块项目分支-检查有什么:gitbranch-a对要包含在拆分中的每个分支进行签出,以在您的工作站上获得本地副本:gitcheckout--trackorigin/branchABC在新目录中创建副本:cp-r oldmultimodsimple进入新项目副本:cd simple删除此项目中不需要的其他模块:git rm other模块1 other2 other3现在只剩下目标模块的子磁盘删除模块子目录,使模块根目录成为新的项目根目录git-mv模块Subdir1/*。删除遗迹子目录:rmdir moduleSubdir1随时检查更改:git状态创建新的git repo并复制其URL以将此项目指向其中:git远程设置url源http://mygithost:8080/git/our-分裂模块回购验证这是否正确:gitremote-v将更改推送到远程存储库:git Push转到远程回购并检查所有内容对所需的任何其他分支重复此操作:git checkout branch2

接下来是github文档“将子文件夹拆分为新存储库”的步骤6-11,以将模块推送到新存储库。

这不会在.git文件夹中节省任何空间,但它会保留这些文件的所有更改历史记录,即使是跨重命名。如果没有“很多”历史记录丢失等,这可能不值得。但至少可以保证您不会丢失以前的提交!

查看git_split项目https://github.com/vangorra/git_split

在自己的位置将git目录转换为自己的存储库。没有子树有趣的业务。该脚本将获取git存储库中的现有目录,并将该目录转换为独立的存储库。在此过程中,它将复制您提供的目录的整个更改历史记录。

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

编辑:添加了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

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

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