将子目录分离(移动)到单独的Git存储库中

将子目录分离(移动)到单独的Git存储库中,git,git-subtree,git-filter-branch,Git,Git Subtree,Git Filter Branch,我有一个包含许多子目录的存储库。现在我发现其中一个子目录与另一个子目录无关,应该分离到单独的存储库 mkdir ~/btoa/ && cd ~/btoa/ git init git pull ~/node-browser-compat btoa-only 如何在保持子目录中文件的历史记录的同时执行此操作 我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在签出较旧版本等时为我提供完整的树。这可能是可以接受的,但我更希望能够假装这两个存储库没有共享的历史 我只想说清楚

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

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only
如何在保持子目录中文件的历史记录的同时执行此操作

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

我只想说清楚,我有以下结构:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/
但我希望这样:

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

更新:这个过程非常常见,git团队使用一个新的工具,
git子树
使它变得更加简单。请看这里:


您希望克隆存储库,然后使用
git filter branch
标记除您希望在新repo中的子目录之外的所有内容

  • 要克隆本地存储库,请执行以下操作:

    git clone /XYZ /ABC
    
    (注意:将使用硬链接克隆存储库,但这不是问题,因为硬链接文件本身不会被修改-将创建新文件。)

  • 现在,让我们保留我们想要重写的有趣分支,然后删除源以避免推送到那里,并确保旧提交不会被源引用:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    
    或对于所有远程分支:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  • 现在,您可能还希望删除与子项目无关的标记;你以后也可以这样做,但你可能需要再次削减回购协议。我没有这样做,并得到了一个
    警告:所有标记的Ref'refs/tags/v0.1'都没有改变
    (因为它们都与子项目无关);此外,移除此类标签后,将回收更多空间。显然,
    git过滤器分支
    应该能够重写其他标记,但我无法验证这一点。如果要删除所有标记,请使用
    git tag-l | xargs git tag-d

  • 然后使用过滤器分支和重置排除其他文件,以便可以修剪它们。我们还将添加
    --标记名筛选器cat--prune empty
    ,以删除空提交并重写标记(请注意,这将不得不去掉它们的签名):

    或者,仅重写头部分支并忽略标记和其他分支:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  • 然后删除备份重日志,以便真正回收空间(尽管现在的操作是破坏性的)

    现在您有了ABC子目录的本地git存储库,它的所有历史都被保留了下来

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

    编辑:合并了下面评论中的各种建议,以确保存储库确实缩小了(以前不总是这样)。

    创建一个包含/ABC的新存储库,但不会从/XYZ中删除/ABC。以下命令将从/XYZ中删除/ABC:

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

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

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

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

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

    i、 e

    $mkdir…ABC.git $cd…ABC.git $git init—裸 在gc修剪之后,还要执行以下操作:

    $ git push ...ABC.git HEAD $git推送…ABC.git头 那你就可以了

    $ git clone ...ABC.git $git克隆…ABC.git 并且ABC/.git的大小减小了

    实际上,push to clean存储库不需要一些耗时的步骤(例如git gc),例如:

    $ git clone --no-hardlinks /XYZ /ABC $ git filter-branch --subdirectory-filter ABC HEAD $ git reset --hard $ git push ...ABC.git HEAD $git克隆--无硬链接/XYZ/ABC $git筛选器分支--子目录筛选器ABC头 $git重置--硬 $git推送…ABC.git头
    我发现,为了正确地从新存储库中删除旧的历史记录,您必须在
    过滤器分支
    步骤之后再做一些工作

  • 执行克隆和筛选操作:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  • 删除对旧历史的所有引用。“origin”跟踪您的克隆,“original”是filter branch保存旧内容的地方:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  • 即使现在,您的历史记录也可能被困在fsck不会触及的打包文件中。将其撕成碎片,创建新的打包文件并删除未使用的对象:

    git repack -ad
    

  • 在.

    更新:git子树模块非常有用,以至于git团队将其拉入核心,并将其制作成
    git子树
    。请看这里:

    git子树在这方面可能很有用

    (已弃用)


    使用此筛选命令删除子目录,同时保留标记和分支:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
    git过滤器分支——索引过滤器\
    “git rm-r-f--cached--ignore unmatch DIR--prune empty”\
    --标签名称过滤器cat--all
    
    编辑:添加了Bash脚本

    这里给出的答案只对我起了部分作用;缓存中保留了许多大文件。最终起作用的(在freenode上的#git中运行数小时后):

    对于以前的解决方案,存储库大小约为100 MB。这一个使它下降到1.7MB。也许这对某人有帮助:)


    下面的bash脚本自动执行
    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'
    
     cd <big-repo>
     git subtree split -P <name-of-folder> -b <name-of-new-branch>
    
     mkdir ~/<new-repo> && cd ~/<new-repo>
     git init
     git pull </path/to/big-repo> <name-of-new-branch>
    
     git remote add origin <git@github.com:user/new-repo.git>
     git push -u origin master
    
     git rm -rf <name-of-folder>
    
    tree ~/node-browser-compat
    
    node-browser-compat
    ├── ArrayBuffer
    ├── Audio
    ├── Blob
    ├── FormData
    ├── atob
    ├── btoa
    ├── location
    └── navigator
    
    cd ~/node-browser-compat/
    git subtree split -P btoa -b btoa-only
    
    mkdir ~/btoa/ && cd ~/btoa/
    git init
    git pull ~/node-browser-compat btoa-only
    
    git remote add origin git@github.com:node-browser-compat/btoa.git
    git push -u origin master
    
    git pull origin master
    git push origin master
    
    git rm -rf btoa
    
    brew install git
    
    sudo apt-get update
    sudo apt-get install git
    git --version
    
    sudo add-apt-repository ppa:git-core/ppa
    sudo apt-get update
    sudo apt-get install git
    
    sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
    sudo ln -s \
    /usr/share/doc/git/contrib/subtree/git-subtree.sh \
    /usr/lib/git-core/git-subtree
    
    git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
    
    git log -- <name-of-folder> # should show nothing
    
    rm -rf .git/refs/original/ && \
    git reflog expire --all && \
    git gc --aggressive --prune=now
    
    git reflog expire --all --expire-unreachable=0
    git repack -A -d
    git prune
    
    git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
    git push <MY_NEW_REMOTE_URL> -f .
    
    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    
    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    
    ./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.
    
     ABC/
        /move_this_dir # did some work here, then renamed it to
    
    ABC/
        /move_this_dir_renamed
    
    git clone git@git.thehost.io:testrepo/test.git
    
    cd test/
    
    rm -r ABC/
    git add .
    enter code here
    git commit -m 'Remove ABC'
    
    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 --delete-folders "{ABC1,ABC2}" metric.git
    
    git log --diff-filter=D --summary | grep delete
    
    remote add origin git@github.com:username/new_repo
    git push -u origin master
    
    # create local clone of original repo in directory XYZ
    tmp $ git clone git@github.com:user/original.git XYZ
    
    # switch to working in XYZ
    tmp $ cd XYZ
    
    # keep subdirectories XY1 and XY2 (dropping ABC)
    XYZ $ git filter-repo --path XY1 --path XY2
    
    # note: original remote origin was dropped
    # (protecting against accidental pushes overwriting original repo data)
    
    # XYZ $ ls -1
    # XY1
    # XY2
    
    # XYZ $ git log --oneline
    # last commit modifying ./XY1 or ./XY2
    # first commit modifying ./XY1 or ./XY2
    
    # point at new hosted, dedicated repo
    XYZ $ git remote add origin git@github.com:user/XYZ.git
    
    # push (and track) remote master
    XYZ $ git push -u origin master
    
    git filter-repo --path XY1 --path XY2 --path inconsistent
    git mv inconsistent XY3  # which updates last modification time
    
    git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3