将子目录从当前 Git 仓库中分离(移出)并创建为独立的 Git 仓库。
将子目录从当前 Git 仓库中分离(移出)并创建为独立的 Git 仓库。
我有一个Git仓库,里面包含许多子目录。现在我发现其中一个子目录与其他内容无关,应分离成一个单独的仓库。
如何在保留子目录文件的历史记录的同时完成此操作?
我想,我可以创建一个克隆,并删除每个克隆中不需要的部分,但我想,这将在检出旧版本时给我完整的树形结构等。这也许可以接受,但我希望能够假装这两个仓库没有分享的历史记录。
只是为了明确我的目标,我有以下结构:
XYZ/ .git/ XY1/ ABC/ XY2/
但我希望变成这样:
XYZ/ .git/ XY1/ XY2/ ABC/ .git/ ABC/
更新: 这个过程非常常见, git 团队创建了一个新的工具 git subtree
来简化它。在这里查看: Detach (move) subdirectory into separate Git repository
您想克隆您的存储库,然后使用 git filter-branch
将除了您在新的存储库中想要的子目录之外的所有内容都标记为垃圾。
-
克隆您的本地存储库:
git clone /XYZ /ABC
(注意: 存储库将使用硬链接克隆,但这不是问题,因为硬链接文件本身不会被修改 - 新文件将被创建。)
-
现在,让我们保留我们要重写的有趣分支,然后移除 origin,避免在那里推送并确保旧提交不会被 origin 引用:
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
的警告 (因为它们都与子项目无关);另外,在删除此类标签后将释放更多的空间。显然,git filter-branch
应该能够重写其他标签,但我无法验证这一点。如果要删除所有标签,请使用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 发现的那样,这将保留新存储库中包含的项目分支和标签。
编辑:从下面的评论中收到的各种建议被合并以确保,例如存储库实际上是缩小了的 (在以前并不总是这样)。
简单的方法
事实证明,这是一种非常常见和有用的做法,Git 的领导人使其变得非常简单,但你必须拥有一个更新版本的 Git(>=2012 年 5 月 1.7.11)。请参阅附录,了解如何安装最新的 Git。下面的步骤演示中还有一个真实世界的例子。
-
准备旧的 repo
cd
git subtree split -P -b
注意:
不能包含前导或尾随字符。例如,名为 subproject
的文件夹必须传递为 subproject
,而不是 ./subproject/
对于 Windows 用户: 当您的文件夹深度>1 时,
必须具有 * nix 样式文件夹分隔符 (/)。例如,名为 path1\path2\subproject
的文件夹必须传递为 path1/path2/subproject
-
创建新的 repo
mkdir ~/
&& cd ~/ git init git pull -
将新的 repo 链接到 GitHub 或任何其他地方
git remote add origin
git push -u origin master -
如果需要,在
中清理git rm -rf
注意:这将保留存储库中的所有历史引用。如果您真正担心提交了密码或需要减小您的.git
文件夹的文件大小,请参见下面的附录。
步骤演示
这些步骤与上面相同,但是按照我的代码库的确切步骤而不是使用
。
这是我用来在 Node.js 中实现 JavaScript 浏览器模块的项目:
tree ~/node-browser-compat node-browser-compat ├── ArrayBuffer ├── Audio ├── Blob ├── FormData ├── atob ├── btoa ├── location └── navigator
我想将单个文件夹 btoa
拆分到一个单独的 Git 存储库中
cd ~/node-browser-compat/ git subtree split -P btoa -b btoa-only
现在,我有一个新的分支 btoa-only
,它只有 btoa
的提交,并且我想创建一个新的存储库。
mkdir ~/btoa/ && cd ~/btoa/ git init git pull ~/node-browser-compat btoa-only
接下来,在 GitHub 或 Bitbucket 等地方创建一个新的 repo 并将其添加为origin
。
git remote add origin git@github.com:node-browser-compat/btoa.git git push -u origin master
愉快的一天!
注意:如果您创建了一个带有 README.md
、.gitignore
和 LICENSE
的存储库,则需要先拉出来:
git pull origin master git push origin master
最后,我想从较大的 repo 中删除该文件夹
git rm -rf btoa
附录
在 macOS 上使用最新的 Git
使用Homebrew获取最新版本的 Git:
brew install git
在 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 filter-branch --prune-empty --tree-filter 'rm -rf' HEAD
之后,您可以检查您的文件或文件夹是否完全不再显示在Git历史记录中
git log --# should show nothing
然而,您无法将删除内容“推送”到GitHub等平台中。如果您尝试,您将收到一个错误,您必须在git push
之前执行git pull
,然后您将回到历史记录中拥有所有内容的状态。
因此,如果您想从“origin”中删除历史记录 - 即要从GitHub、Bitbucket等平台中删除它 - 您需要删除该repo并重新推送修剪过的repo的副本。但等等 - 还有更多! - 如果您真的担心要删除密码或类似的内容,您需要修剪备份(见下文)。
让.git
文件夹更小
前面提到的删除历史命令仍然会留下一堆备份文件 - 因为Git在帮助你不会意外破坏repo时非常友好。它最终会在几天或几个月后删除孤儿文件,但它会在一段时间内将它们保留下来,以防您意识到您意外删除了某些您不想要的东西。
因此,如果您真的想要清空垃圾并立即减小repo的克隆大小,您必须执行所有这些非常奇怪的操作:
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
话虽如此,除非您知道自己需要这样做 - 以防您修剪了错误的子目录,您不应执行这些步骤。备份文件不应在推送repo时被克隆,它们只会存在于您的本地副本中。