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

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

我该如何做到这一点,同时保持子目录内的文件的历史?

我想我可以做一个克隆并删除每个克隆中不需要的部分,但是我想这会在检查一个较旧的修订时给我一个完整的树。这可能是可以接受的,但我更愿意能够假装两个存储库没有共享历史记录。

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想代之以:

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

更新 :这个过程非常常见,git团队使用新工具git subtree更简单。 看到这里:分离(移动)到不同的Git仓库的子目录


你想克隆你的仓库,然后使用git filter-branch来标记你的新仓库中的所有子目录,但垃圾收集。

  • 克隆您的本地存储库:

    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
    
  • 现在您可能还想删除与子项目无关的标签; 您也可以稍后再做,但您可能需要再次修剪您的回购。 我没有这样做,并得到一个WARNING: Ref 'refs/tags/v0.1' is unchanged对所有标签WARNING: Ref 'refs/tags/v0.1' is unchanged (因为它们都与子项目无关)。 此外,删除这些标签后,将回收更多空间。 显然, git filter-branch应该能够重写其他标记,但我无法验证这一点。 如果你想删除所有标签,请使用git tag -l | xargs git tag -d git tag -l | xargs git tag -d

  • 然后使用filter-branch并重置以排除其他文件,以便它们可以被修剪。 我们还要添加--tag-name-filter cat --prune-empty来删除空的提交并重写标签(注意,这将不得不删除它们的签名):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    或者也可以只重写HEAD分支并忽略标签和其他分支:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  • 然后删除备份reflogs,以便可以真正回收空间(尽管现在该操作具有破坏性)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    现在你有一个保存所有历史记录的ABC子目录的本地git存储库。

  • 注意:对于大多数用途, git filter-branch确实应该有添加的参数-- --all 。 是的,这真的冲冲刺空间破折号破折号all 。 这需要是该命令的最后一个参数。 正如Matli发现的那样,这可以让项目分支和标签包含在新回购中。

    编辑:以下评论的各种建议被合并,以确保,例如,存储库实际上是收缩(这并非总是如此)。


    Easy Way™

    事实证明,这是一种常见而有用的做法,git的主人使它变得很容易,但是你必须有一个更新版本的git(> = 1.7.11 May 2012)。 请参阅附录以了解如何安装最新的git。 另外,下面的演练中还有一个真实世界的例子

  • 准备旧的回购

    pushd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    注意: <name-of-folder>不能包含前导字符或结尾字符。 例如,名为subproject的文件夹务必作为subproject传递,而不是./subproject/

    Windows用户注意:当文件夹深度> 1时, <name-of-folder>必须包含* nix样式的文件夹分隔符(/)。 例如,名为path1path2subproject的文件夹务必作为path1/path2/subproject传递

  • 创建新的回购

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  • 将新的回购链接链接到Github或任何地方

    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文件夹的文件大小,请参阅下面的附录

  • ...

    演练

    这些步骤与上述步骤相同 ,但按照我的存储库的确切步骤,而不是使用<meta-named-things>

    这是我在节点中实现JavaScript浏览器模块的一个项目:

    tree ~/Code/node-browser-compat
    
    node-browser-compat
    ├── ArrayBuffer
    ├── Audio
    ├── Blob
    ├── FormData
    ├── atob
    ├── btoa
    ├── location
    └── navigator
    

    我想将单个文件夹btoa拆分为单独的git存储库

    pushd ~/Code/node-browser-compat/
    git subtree split -P btoa -b btoa-only
    popd
    

    我现在有一个新的分支, btoa-only ,只有提交btoa ,我想创建一个新的存储库。

    mkdir ~/Code/btoa/
    pushd ~/Code/btoa/
    git init
    git pull ~/Code/node-browser-compat btoa-only
    

    接下来,我在Github或bitbucket上创建一个新的回购协议,或者添加它是origin (btw,“origin”只是一个约定,不是命令的一部分 - 你可以称之为“remote-server”或任何你喜欢的东西)

    git remote add origin git@github.com:node-browser-compat/btoa.git
    git push origin -u master
    

    快乐的一天!

    注意:如果您使用README.md.gitignoreLICENSE创建了回购,您需要先README.md

    git pull origin -u master
    git push origin -u master
    

    最后,我想从更大的回购中删除文件夹

    git rm -rf btoa
    

    ...

    附录

    OS X上的最新git

    要获取最新版本的git:

    brew install git
    

    为了酿造OS X:

    http://brew.sh

    Ubuntu上最新的git

    sudo apt-get update
    sudo apt-get install git
    git --version
    

    如果这不起作用(你有一个非常旧的版本的Ubuntu),请尝试

    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
    

    感谢评论中的rui.araujo。

    清除你的历史

    默认情况下,从git中移除文件实际上并没有将它们从git中移除,它只是承诺它们不在那里。 如果你想实际删除历史引用(即你有一个提交的密码),你需要这样做:

    git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
    

    之后,你可以检查你的文件或文件夹不再出现在git历史记录中

    git log -- <name-of-folder> # should show nothing
    

    然而,你不能“推”删除github等。 如果你尝试,你会得到一个错误,你就必须git pull ,然后才能git push -然后你又回到了在你的历史的一切。

    所以如果你想从“origin”中删除历史记录 - 意味着从github,bitbucket等中删除它 - 你需要删除repo并重新推回修剪后的repo。 但是等等 - 还有更多 ! - 如果你真的担心摆脱密码或类似的东西,你需要修剪备份(见下文)。

    使得.git变小

    前面提到的删除历史记录命令仍然留下了一堆备份文件 - 因为git非常善意地帮助您不会意外毁掉您的回购站。 它最终会在几天和几个月内删除孤立的文件,但它会让它们留下一段时间,以防您意识到您意外删除了您不想要的内容。

    所以如果你真的想清空垃圾来立即减少回购的克隆大小 ,你必须做所有这些真正奇怪的事情:

    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
    

    也就是说,我建议不要执行这些步骤,除非你知道你需要 - 如果你修剪了错误的子目录,你知道吗? 备份文件在推送回购时不应克隆,它们只会存放在本地副本中。

    信用

  • http://psionides.eu/2010/02/04/sharing-code-between-projects-with-git-subtree/
  • 从git中永久删除一个目录
  • http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/
  • 如何从我的git回购中删除未引用的blob

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

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

    当然,首先在'clone - no-hardlinks'存储库中进行测试,然后按照Paul列出的reset,gc和prune命令进行测试。

    链接地址: http://www.djcxy.com/p/7841.html

    上一篇: Detach (move) subdirectory into separate Git repository

    下一篇: allow folder creation but ignore all files inside