一尘不染

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

git

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

如何在将文件历史记录保留在子目录中的同时做到这一点?

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想要这个:

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

阅读 154

收藏
2022-02-15

共2个答案

一尘不染

事实证明,这是一种常见且有用的做法,Git 的霸主们让它变得非常容易,但你必须拥有更新版本的 Git (>= 1.7.11 May 2012)。有关如何安装最新 Git 的信息,请参阅附录。*此外,下面的**演练中有一个真实的示例*。

  1. 准备旧仓库

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

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

Windows 用户注意事项:当您的文件夹深度大于 1 时,<name-of-folder>必须使用 *nix 样式的文件夹分隔符 (/)。例如,名为的文件夹path1\path2\subproject必须作为path1/path2/subproject

  1. 创建新的仓库

mkdir ~/<new-repo> && cd ~/<new-repo> git init git pull </path/to/big-repo> <name-of-new-branch>

  1. 将新的 repo 链接到 GitHub 或任何地方

git remote add origin <git@github.com:user/new-repo.git> git push -u origin master

  1. 如果需要<big-repo>,清理内部

git rm -rf <name-of-folder>

注意:这会在存储库中留下所有历史参考。如果您确实担心已提交密码或需要减小文件夹的文件大小,请参阅下面的附录.git


练习

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

这是我在节点中实现 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 或其他任何地方创建一个新的存储库并将其添加为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

最后,我想从更大的仓库中删除该文件夹

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

清除您的历史记录

默认情况下,从 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- 然后你又回到你的历史中。

因此,如果您想从“起源”中删除历史记录(即从 GitHub、Bitbucket 等中删除),您需要删除存储库并重新推送已修剪的存储库副本。但是等等 -还有更多!- 如果您真的担心删除密码或类似的东西,您需要修剪备份(见下文)。

减少.git

前面提到的删除历史命令仍然会留下一堆备份文件——因为 Git 非常友好地帮助您避免意外破坏您的存储库。它最终会在几天和几个月内删除孤立的文件,但如果您意识到您不小心删除了您不想删除的内容,它会将它们保留一段时间。

因此,如果您真的想立即清空垃圾箱减少 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

也就是说,除非你知道你需要这样做,否则我建议不要执行这些步骤——以防万一你修剪了错误的子目录,你知道吗?推送存储库时不应克隆备份文件,它们只会在您的本地副本中。

2022-02-15
一尘不染

您想要克隆您的存储库,然后使用git filter-branch标记除您希望在新存储库中被垃圾收集的子目录之外的所有内容。

  1. 要克隆本地存储库:

git clone /XYZ /ABC

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

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

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

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

  2. 然后使用 filter-branch 和 reset 排除其他文件,以便修剪它们。让我们还添加--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

  1. 然后删除备份的 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。是的,确实如此–space– all。这需要是命令的最后一个参数。正如 Matli 发现的那样,这会使项目分支和标签包含在新的 repo 中。

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

2022-02-15