将子目录分离(移动)到单独的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
, .gitignore
和LICENSE
创建了回购,您需要先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
也就是说,我建议不要执行这些步骤,除非你知道你需要 - 如果你修剪了错误的子目录,你知道吗? 备份文件在推送回购时不应克隆,它们只会存放在本地副本中。
信用
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