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

如何在保留子目录中文件的历史记录的同时执行此操作?

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

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

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

当前回答

我发现,为了从新存储库中正确删除旧的历史记录,在过滤器分支步骤之后,您必须做更多的工作。

执行克隆和筛选:gitclone--没有硬链接foo bar;cd条gitfilter分支--子目录filter subdir/you/wente删除对旧历史的所有引用。“origin”是跟踪您的克隆,“original”是过滤器分支保存旧内容的位置:git远程rm源git update ref-d refs/original/refs/heads/mastergit reflog expire--expire=现在--全部即使是现在,您的历史记录也可能被保存在fsck不会触及的文件包中。将其撕成碎片,创建新的打包文件并删除未使用的对象:git重新打包-ad

过滤器分支手册中对此进行了解释。

其他回答

简单的方法™

事实证明,这是一种非常普遍和有用的做法,Git的霸主们让它变得非常简单,但你必须有一个新版本的Git(>=1.7.11 May 2012)。有关如何安装最新的Git,请参阅附录。此外,下面的演练中有一个真实世界的示例。

准备旧回购cd<大回购>git子树拆分-P<文件夹名称>-b<新分支名称>

注意:<文件夹名称>不能包含前导或尾随字符。例如,名为subject的文件夹必须作为子项目传递,而不是/子项目/

Windows用户注意:当文件夹深度>1时,<文件夹名称>必须具有*nix样式的文件夹分隔符(/)。例如,名为path1\path2\subject的文件夹必须作为path1/path2/subject传递

创建新回购mkdir~/<new repo>&&cd~/<newrepo>初始化git pull</path/to/big repo><新分支的名称>将新回购链接到GitHub或任何地方git远程添加原点<git@github.com:user/new repo.git>git push-u原始主机如果需要,清理<big repo>内部gitrm-rf<文件夹名称>

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


演练

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

下面是我在node中实现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,它只有btoa的提交,我想创建一个新存储库。

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

接下来,我在GitHub或Bitbucket上创建一个新的repo,并将其添加为源代码

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

快乐的一天!

注意:如果您使用README.md、.gitignore和LICENSE创建了一个repo,则需要首先执行以下操作:

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等。如果你尝试了,你会得到一个错误,你必须先得到pull,然后才能得到push,然后你就回到了你的历史中。

因此,如果你想从“源”中删除历史记录-意思是从GitHub、Bitbucket等中删除它-你需要删除回购,并重新推送一个经过修剪的回购副本。但等等-还有更多!-如果你真的担心删除密码或类似的东西,你需要删除备份(见下文)。

使.git变小

前面提到的delete history命令仍然会留下一堆备份文件,因为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 repo中删除未引用的Blob

更新:git子树模块非常有用,以至于git团队将其拉入核心并使其成为git子树。请参阅此处:将子目录分离(移动)到单独的Git存储库中

git子树可能对此有用

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt(已弃用)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

发现这篇精彩的文章原创参考很容易理解。记录在这里,以防无法访问。

1.准备当前存储库

$ cd path/to/repository
$ git subtree split -P my-folder -b my-folder
Created branch 'my-folder'
aecbdc3c8fe2932529658f5ed40d95c135352eff

文件夹的名称必须是相对路径,从存储库的根目录开始。

2.创建新存储库

$ cd my-folder
$ git init
Initialized empty Git repository in /Users/adamwest/Projects/learngit/shop/my-folder/.git/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 192c10b] initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file

这里我们只需要cd到新文件夹,初始化新存储库,并提交任何内容。

3.添加新的远程存储库并推送

$ git remote add origin git@github.com:robertlyall/my-folder.git
$ git push origin -u master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 199 bytes | 199.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:robertlyall/my-folder.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

我们在这里添加远离GitHub的新存储库,然后推送我们的第一次提交。

4.从主存储库中删除文件夹并推送

$ cd ../
$ git rm -rf my-folder
rm 'my-folder/file'
$ git commit -m "Remove old folder"
[master 56aedbe] remove old folder
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 my-folder/file
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 217 bytes | 217.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:robertlyall/shop.git
   74dd8b3..56aedbe  master -> master

最后,我们cd回到rooot目录,从主存储库中删除文件夹,然后提交并推送更改。现在,我们的主存储库中有一个文件夹,但它链接到一个完全独立的存储库,可以在多个项目中重用。

把这个放到你的gitconfig中:

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'

编辑:添加了Bash脚本。

这里给出的答案对我来说只是部分奏效;缓存中仍有大量大文件。什么最终奏效了(在freenode上的#git中的几个小时后):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

在以前的解决方案中,存储库大小约为100 MB。这一次将其降至1.7 MB。也许这对某人有帮助:)


以下bash脚本自动执行任务:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now