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

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

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

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

当前回答

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

其他回答

正确的方法如下:

git filter branch--修剪空--子目录筛选器FOLDER_NAME[first_branch][another_branch]

GitHub现在甚至有关于此类案例的小文章。

但一定要先将原始存储库克隆到单独的目录中(因为这会删除所有文件和其他目录,您可能需要使用它们)。

所以你的算法应该是:

将远程存储库克隆到另一个目录使用gitfilter分支只在某些子目录下留下文件,推送到新的远程创建提交以从原始远程存储库中删除此子目录

把这个放到你的gitconfig中:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

我发现,为了从新存储库中正确删除旧的历史记录,在过滤器分支步骤之后,您必须做更多的工作。

执行克隆和筛选:gitclone--没有硬链接foo bar;cd条gitfilter分支--子目录filter subdir/you/wente删除对旧历史的所有引用。“origin”是跟踪您的克隆,“original”是过滤器分支保存旧内容的位置:git远程rm源git update ref-d refs/original/refs/heads/mastergit reflog expire--expire=现在--全部即使是现在,您的历史记录也可能被保存在fsck不会触及的文件包中。将其撕成碎片,创建新的打包文件并删除未使用的对象:git重新打包-ad

过滤器分支手册中对此进行了解释。

更新: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/

更简单的方法

安装git拆分。我基于jkeating的解决方案将其创建为git扩展。将目录拆分为本地分支#更改到回购的目录cd/path/to/repo#检查分支机构数字结帐XYZ#将多个目录拆分为新的分支XYZ数字拆分-b XYZ XY1 XY2在某处创建空回购。我们假设在GitHub上创建了一个名为xyz的空repo,其路径为:git@github.com:simpliwp/xyz.git推送至新回购。#为空回购添加一个新的远程源,以便我们可以在GitHub上推送到空回购git远程添加origin_xyzgit@github.com:simpliwp/xyz.git#将分支推送到空回购的主分支git push origin_xyz xyz:master将新创建的远程存储库克隆到新的本地目录#将当前目录更改为旧的存储库cd/path/to/where/you/want/the/new/local/repo#克隆您刚刚推送到的远程存储库git克隆git@github.com:simpliwp/xyz.git