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

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

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

为了明确起见,我具有以下结构:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/


但我想改成这样:

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


评论

现在,这对于git filter-branch来说是微不足道的,请参阅下面的答案。

@jeremyjjbrown是正确的。这不再困难,但是很难在Google上找到正确的答案,因为所有旧答案都主导结果。

#1 楼

更新:这个过程非常普遍,以至于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(因为它们都与子项目无关);此外,删除此类标签后,将回收更多空间。显然,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



然后删除备份引用日志,以便可以真正回收空间(尽管现在该操作具有破坏性)

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所发现的,这将使项目分支和标签保留在新仓库中。

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

评论


为什么需要-无需硬链接?删除一个硬链接不会影响另一个文件。 Git对象也是不可变的。仅当您要更改所有者/文件权限时,才需要--no-hardlinks。

–vdboor
2010-2-1在9:58



如果要重写标签以不引用旧结构,请添加--tag-name-filter cat

–马尔科姆盒子
2011年10月6日上午11:58

像Paul一样,我不想在新的仓库中使用项目标签,所以我不使用--all。我还运行了git remote rm origin和git tag -l | git filter-branch命令之前的xargs git tag -d。这将我的.git目录从60M缩小到了300K。请注意,我需要同时运行这两个命令以减小尺寸。

–盐烯
11年11月17日在21:18

git手册页建议使用git for-each-ref --format =“%(refname)” refs / original / |代替rm -rf .git / refs / original / | xargs -n 1 git update-ref -d;我猜如果引用没有存储在正确的位置,后者会更健壮。此外,我认为还需要'git remote rm origin'来缩小存储库,否则来自origin的引用将保留引用的对象。 @jonp,我认为这是您的问题。最后,要重写其他分支,必须在cloninng---all之后使用git branch手动设置它们,并删除HEAD(停止重写其他分支)。

–布莱布莱德
2012-2-20在0:07

这不会创建ABC /而不是ABC / ABC /吗?

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
13年5月21日在8:39

#2 楼

Easy Way™
事实证明,这是一种通用且有用的做法,Git的霸主确实使这变得非常容易,但是您必须拥有更新版本的Git(> = 2012年5月1.7.11)。有关如何安装最新版本的Git的信息,请参阅附录。另外,下面的演练中有一个真实的示例。


准备旧的仓库
 cd <big-repo>
 git subtree split -P <name-of-folder> -b <name-of-new-branch>



注意:<name-of-folder>必须不包含前导或尾随字符。例如,名为subproject的文件夹必须作为subproject传递,而不是./subproject/
Windows用户注意:当文件夹深度> 1时,<name-of-folder>必须具有* nix样式文件夹分隔符(/)。例如,名为path1\path2\subproject的文件夹必须作为path1/path2/subproject传递


创建新的仓库
 mkdir ~/<new-repo> && cd ~/<new-repo>
 git init
 git pull </path/to/big-repo> <name-of-new-branch>



将新仓库链接到GitHub或任何地方
 git remote add origin <git@github.com:user/new-repo.git>
 git push -u origin master



如果需要,清理<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
快乐的一天!
注意:如果您使用README.md.gitignoreLICENSE创建了存储库,则需要首先拉:
git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master

最后,我要从更大的存储库中删除文件夹
git pull origin master
git push origin master


附录
macOS上的最新Git
使用Homebrew获得最新版本的Git:
git rm -rf btoa

Ubuntu上的最新Git
brew install git

如果没有工作(您有一个非常旧的Ubuntu版本),请尝试
sudo apt-get update
sudo apt-get install git
git --version

如果仍然不起作用,请尝试
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

感谢评论中的rui.araujo。
清除您的历史记录
默认情况下,从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

但是,您不能将删除操作“推送”到GitHub之类。如果尝试尝试,将出现错误,并且必须先进行git pull才能执行-然后返回到历史记录中的所有内容。
因此,如果要从“起源”中删除历史记录“-意味着要从GitHub,Bitbucket等中将其删除-您需要删除该存储库,然后重新推送该存储库的删节副本。但是,等等-还有更多! -如果您确实担心要删除密码或类似的东西,则需要修剪备份(请参见下文)。
git push缩小一些
上述删除历史记录命令仍然会留下一堆备份文件-因为Git太善于帮助您避免意外损坏存储库。它最终将在几天和几个月内删除孤立的文件,但是会保留一段时间,以防万一您意外删除了不想删除的文件。
因此,如果您真的想清空垃圾箱,立即减小存储库的克隆大小,您必须做所有这些非常奇怪的事情:
git log -- <name-of-folder> # should show nothing

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

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 subtree仍然是'contrib'文件夹的一部分,并且并非默认安装在所有发行版中。 github.com/git/git/blob/master/contrib/subtree

–洋葱洋葱
2013年8月2日14:06



@krlmlr 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在Ubuntu 13.04上激活

–rui.araujo
13年8月26日在6:39



如果您已将密码推送到公共存储库,则应更改密码,而不要尝试从公共存储库中删除密码,并希望没有人看到它。

–Miles Rout
2013年9月18日下午3:43

此解决方案不保留历史记录。

–Cœur
2015年11月11日在2:06

popd和pushd命令使此隐式且难以理解它打算做什么...

–jones77
18年6月4日在16:39

#3 楼

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

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


当然,请首先在“克隆--no-hardlinks”存储库中对其进行测试,然后对其进行跟踪使用reset,gc和prune命令Paul列出。

评论


使该git filter-branch --index-filter“ git rm -r -f -cached --ignore-unmatch ABC” –prune-empty HEAD将会更快。索引过滤器对索引起作用,而树过滤器必须为每次提交检出并暂存所有内容。

– fmarc
09年9月17日在19:58

在某些情况下,弄乱存储库XYZ的历史记录是过大的事情……对于大多数人来说,简单的“ rm -rf ABC; git rm -r ABC; git commit -m'将ABC提取到自己的存储库中”会更好。

–Evgeny
2010-10-28 23:24

如果您不止一次执行此命令,则可能希望在此命令上使用-f(force),例如,在两个目录分开后将其删除。否则,您将收到“无法创建新备份”。

–布赖恩·卡尔顿(Brian Carlton)
2011-4-18 17:59



如果正在执行--index-filter方法,则可能还需要创建git rm -q -r -f,这样每次调用都不会为删除的每个文件打印一行。

–埃里克·内塞斯(Eric Naeseth)
2011-10-12 19:55

我建议编辑保罗的答案,只是因为保罗的答案如此详尽。

– Erik Aronesty
2014年5月5日15:38

#4 楼

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



执行克隆和过滤器:

git clone --no-hardlinks foo bar; cd bar
git filter-branch --subdirectory-filter subdir/you/want



删除所有对旧历史的引用。 “ origin”一直在跟踪您的克隆,而“ original”是过滤器分支保存旧内容的地方:可能卡在了fsck无法触及的packfile中。将其撕成碎片,创建一个新的packfile并删除未使用的对象:

git remote rm origin
git update-ref -d refs/original/refs/heads/master
git reflog expire --expire=now --all



在分支过滤器手册中对此有一个解释。

评论


我认为像git gc --aggressive --prune = now仍然不见了,不是吗?

–阿尔伯特
2012年7月11日在20:14

@Albert repack命令可以解决此问题,并且不会有任何松散的物体。

–李Jo
2012年7月11日在20:57

是的,git gc --aggressive --prune = now减少了很多新的仓库

–托梅克·维德卡
13年4月2日在9:01

简洁大方。谢谢!

– Marco Pelegrini
16年8月29日在2:30

毕竟,我仍然遇到与以前相同的错误。致命:打包对象xxxxxx(存储在.git / objects / pack / pack-yyyyyyyy.pack中)已损坏

– AaA
9月2日7:17

#5 楼

编辑:添加了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:   q4312078q </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: q4312078q /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/Clone
newN=/tmp/

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

git filter-branch --subdirectory-filter   --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


#6 楼

这不再那么复杂,您只需在回购的克隆上使用git filter-branch命令即可剔除不需要的子目录,然后将其推送到新的远程服务器。

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .


评论


这就像一个魅力。上例中的YOUR_SUBDIR是您要保留的子目录,其他所有内容都将被删除

– J.T.泰勒
2015年4月19日在6:18

根据您的评论进行更新。

– jeremyjjbrown
15年4月20日在18:43

这不能回答问题。从文档中说,结果将包含该目录(并且仅包含该目录)作为其项目根目录。实际上,这就是您将获得的,即,未保留原始项目结构。

– NicBright
17年2月2日在13:11

@NicBright您可以在问题中说明XYZ和ABC的问题,以说明问题所在吗?

–亚当
17-10-26在16:02

@jeremyjjbrown是否可以重复使用克隆的存储库而不使用新的存储库,即我在这里的问题stackoverflow.com/questions/49269602/…

–秋浪
18年4月3日在12:34

#7 楼

更新:git-subtree模块非常有用,以至于git团队将其拉入核心并使其成为git subtree。请参阅此处:将子目录分离(移动)到单独的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/

评论


git-subtree现在是Git的一部分,尽管它在contrib树中,所以默认情况下并不总是安装。我知道它是由Homebrew git公式安装的,但没有手册页。因此,apenwarr称其版本已过时。

–echristopherson
13年5月10日在16:04

#8 楼

这是对CoolAJ86的“ The Easy Way™”答案的较小修改,目的是将多个子文件夹(例如sub1sub2)拆分到一个新的git存储库中。

The Easy Way™(多个子文件夹) )



准备旧仓库

pushd <big-repo>
git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
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样式的文件夹分隔符(/)。例如,名为path1\path2\subproject的文件夹必须作为path1/path2/subproject传递。此外,请不要使用mv命令,而要使用move

最后一点:与基本答案的唯一不同之处是脚本“ git filter-branch...”的第二行


创建新存储库

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文件夹的文件大小。



评论


稍加修改,这对我有用。因为我的sub1和sub2文件夹在初始版本中不存在,所以我必须按如下方式修改--tree-filter脚本:“ mkdir <文件夹名称>;如果[-d sub1];则mv <文件夹名称> /; fi”。对于第二个filter-branch命令,我用替换了,省略了的创建,并在filter-branch之后添加了-f来覆盖现有备份的警告。

– pglezen
16-2-11在19:38



如果在git的历史记录中任何子目录都发生了更改,则此方法将无效。如何解决呢?

–nietras
16 Mar 3 '16 at 12:06

@nietras看到rogerdpack的答案。在阅读并吸收了其他答案中的所有信息之后,花了我一段时间才能找到它。

–亚当
17-10-30在17:18

#9 楼

当使用较新版本的git filter-branch(也许是git?)运行2.22+时,它说要使用此新工具git-filter-repo。该工具肯定可以简化我的工作。

使用filter-repo进行过滤

从原始问题创建XYZ repo的命令:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master


假设:
*远程XYZ回购在推送之前是新的并且是空的

过滤和移动

我也想移动几个目录以获得更一致的结构。最初,我先运行了一个简单的filter-repo命令,然后运行了git mv dir-to-rename,但是我发现使用--path-rename选项可以得到稍微更好的历史记录。现在我看到的是5 hours ago(在GitHub UI中),而不是在新存储库中看到对移动文件的最后修改的last year,它与原始存储库中的修改时间匹配。

而不是...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time


我最终跑了...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3


注意:


我认为Git Rev News博客文章很好地解释了创建另一个回购过滤工具背后的原因。我最初尝试了在原始存储库中创建与目标回购名称匹配的子目录的路径,然后进行过滤(使用git filter-repo --subdirectory-filter dir-matching-new-repo-name)。该命令正确地将该子目录转换为复制的本地存储库的根目录,但是它还导致创建该子目录只进行了三次提交的历史记录。 (我还没有意识到可以多次指定--path,从而避免了在源存储库中创建子目录的需要。)由于有人在提交时已经提交了源存储库,所以我注意到我无法继续进行历史,我只是在git reset commit-before-subdir-move --hard命令后使用了clone,并在--force命令中添加了filter-repo,以使其能够在经过稍微修改的本地克隆上运行。

 git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
 



由于不了解git的扩展模式,我对安装感到很困惑,但最终我克隆了git-filter-repo并将其符号链接到$(git --exec-path)

 ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
 


评论


因推荐新的filter-repo工具(我上个月在stackoverflow.com/a/58251653/6309中提出)而获得推荐

–VonC
19年11月21日在22:03



在这一点上,绝对应该首选使用git-filter-repo。它比git-filter-branch更快,更安全,并且可以防止很多人在重写git历史记录时遇到麻烦。希望这个答案能引起更多关注,因为它是解决git-filter-repo的问题。

–杰里米·卡尼(Jeremy Caney)
19/12/28在9:14

#10 楼

最初的问题是希望XYZ / ABC /(* files)成为ABC / ABC /(* files)。在为我自己的代码实现可接受的答案后,我注意到它实际上将XYZ / ABC /(* files)更改为ABC /(* files)。过滤器分支的手册页甚至说:


结果将包含该目录(并且仅包含该目录)作为其项目根。”


换句话说,它可以将顶级文件夹“上”提升一个级别,这是一个重要的区别,因为例如,在我的历史中,我已将顶级文件夹重命名。通过将文件夹“上”提升一个级别,git失去了连续性在我进行了重命名的提交处。



然后我对问题的回答是制作存储库的2个副本并手动删除所需的文件夹手册页对此进行了支持:


[...]如果仅需一次简单的提交就可以解决您的问题,请避免使用[this command]


评论


我喜欢该图的样式。请问您使用的是什么工具?

– Slipp D. Thompson
13年3月30日在18:17

Mac版塔。我很喜欢。就其本身而言,几乎值得切换到Mac。

– MM。
13年4月2日在21:02

是的,尽管就我而言,我的子文件夹targetdir在某个时候已被重命名,而git filter-branch只是简单地将其命名为一天,删除了重命名之前所做的所有提交!令人震惊的是,考虑到Git在跟踪此类事情,甚至单个内容块的迁移方面多么熟练!

–杰伊·艾伦(Jay Allen)
13年5月31日在9:25

哦,同样,如果有人发现自己在同一条船上,这就是我使用的命令。不要忘记git rm需要多个参数,因此没有理由为每个文件/文件夹运行它:BYEBYE =“ dir / subdir2 dir2 file1 dir / file2”; git filter-branch -f --index-filter“ git rm -q -r -f --cached --ignore-unmatch $ BYEBYE” –修剪空---all

–杰伊·艾伦(Jay Allen)
13年5月31日在9:26



#11 楼

为了补充Paul的答案,我发现要最终恢复空间,我必须将HEAD推送到一个干净的存储库中,并缩小.git / objects / pack目录的大小。

ie

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare


gc修剪后,还可以执行以下操作:

$ git push ...ABC.git HEAD


然后就可以做

/>
$ git clone ...ABC.git


减小了ABC / .git的大小

实际上,按一下并不需要某些耗时的步骤(例如git gc)干净的存储库,即:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD


#12 楼

现在正确的方法如下:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub现在甚至有关于此类情况的小文章。

,但是一定要克隆原始仓库首先将目录分开(因为这将删除所有文件和其他目录,并且您可能需要使用它们)。

所以您的算法应为:


使用git filter-branch将您的远程存储库克隆到另一个目录
,只保留某个子目录下的文件,推送到新的远程存储
创建提交,以从原始远程存储库中删除此子目录


#13 楼

我建议使用GitHub的指南将子文件夹拆分为新的存储库。这些步骤与Paul的回答相似,但是我发现它们的说明更易于理解。

我已经修改了这些说明,以便它们适用于本地存储库,而不是托管在GitHub上的存储库。



将子文件夹拆分为新的存储库


打开Git Bash。
将当前工作目录更改为您所在的位置要创建新的存储库。
克隆包含子文件夹的存储库。



 git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
 



将当前工作目录更改为克隆的存储库。



 cd REPOSITORY-NAME
 



要从存储库中其余文件中过滤出子文件夹,请运行git filter-branch,并提供以下信息:



FOLDER-NAME:要在项目中创建单独存储库的文件夹



提示:Windows用户应使用/来分隔文件夹。



BRANCH-NAME:当前版本的默认分支项目,例如mastergh-pages





 git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
  


评论


不错的帖子,但我注意到您链接的文档的第一段说,如果您创建存储库的新克隆,那么在将文件夹拆分为单独的存储库时,您不会丢失任何Git历史记录或更改。但是,根据此处所有答案的注释,无论子目录已重命名,filter-branch和subtree脚本都会导致历史记录丢失。有什么可以解决的吗?

–亚当
17-10-30在11:53

找到了保留所有提交的解决方案,包括先前目录的重命名/移动-这是rogerdpack回答此问题的方法。

–亚当
17-10-30在17:42

唯一的问题是我不能再使用克隆的仓库

–秋浪
18年4月3日在13:17

#14 楼

看来,这里的大多数(全部?)答案都取决于某种形式的git filter-branch --subdirectory-filter及其同类。这可能在大多数情况下有效,但是在某些情况下,例如,当您重命名文件夹时,例如:文件更改历史最初是从“ move_this_dir”(参考)开始出现的。
因此,看来,实际上保留所有更改历史的唯一方法(如果您的情况是这样的话),实质上是复制存储库(创建一个新存储库,将其设置为原始存储库),然后对其他所有内容进行核对,然后将子目录重命名为父目录,如下所示:

在本地克隆多模块项目
分支-检查其中:git branch -a

对要包含在拆分中的每个分支进行签出,以在工作站上获取本地副本:git checkout --track origin/branchABC

在其中复制一个新目录:cp -r oldmultimod simple

进入新项目副本:cd simple

摆脱该项目中不需要的其他模块:
git rm otherModule1 other2 other3 < br / >现在仅保留目标模块的子目录
摆脱模块子目录,使模块根成为新的项目根
git mv moduleSubdir1/* .
删除遗物子目录:rmdir moduleSubdir1
随时检查更改:git status

创建新的git repo并复制其URL以将该项目指向该项目:
git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo git remote -v

将更改推送到远程存储库:git push

转到远程存储库并检查是否全部存在
对其他分支重复该操作:git checkout branch2


这是在github文档“将子文件夹拆分到新的存储库中”之后的步骤6-11,将模块推送到新的存储库。
这不会在.git文件夹中节省任何空间,但即使在重命名之间,也将保留这些文件的所有更改历史记录。如果没有丢失很多“历史记录”等,这可能不值得。但是至少可以保证您不会丢失较早的提交!

评论


在git haystack中找到了针!现在,我可以保留所有提交历史记录。

–亚当
17-10-30在17:19

#15 楼

我确实有这个问题,但是所有基于git filter-branch的标准解决方案都非常慢。如果您的存储库较小,那么这可能不成问题,这对我来说是正确的。我编写了另一个基于libgit2的git过滤程序,该程序首先为主存储库的每次过滤创建分支,然后将其推送到清理存储库作为下一步。在我的存储库(500Mb 100000提交)上,标准的git filter-branch方法花了几天的时间。我的程序需要几分钟才能完成相同的过滤。

它的名字叫git_filter,它位于这里:

https://github.com/slobobaby/git_filter

在GitHub上。

我希望它对某人有用。

#16 楼

使用此过滤器命令删除子目录,同时保留标签和分支:

 git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
 


评论


这里的猫是什么?

–rogerdpack
16-09-19在18:35

#17 楼

值得一提的是,这是在Windows计算机上使用GitHub的方式。假设您有一个克隆的仓库位于C:\dir1中。目录结构如下所示:C:\dir1\dir2\dir3dir3目录是我想成为新的单独存储库的目录。

Github:


创建新的存储库:MyTeam/mynewrepo


提示提示:


$ cd c:/Dir1
$ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
返回:Ref 'refs/heads/master' was rewritten(仅供参考:dir2 / dir3区分大小写。)
$ git remote add some_name git@github.com:MyTeam/mynewrepo.git git remote add origin etc。无效,返回“ remote origin already exists
$ git push --progress some_name master


#18 楼

正如我上面提到的,我不得不使用反向解决方案(删除所有未触及我的dir/subdir/targetdir的提交),这看起来效果很好,可以删除大约95%的提交(根据需要)。但是,仍然存在两个小问题。

首先,filter-branch删除了引入或修改代码的提交,但显然,合并提交位于Gitiverse中。


屏幕快照:合并疯狂!

这是一个我可以忍受的修饰性问题(他说...眼睛慢慢移开)。

第二个剩余的提交几乎都是重复的!我似乎已经获得了第二条冗余的时间表,该时间表几乎涵盖了项目的整个历史。有趣的是(您可以从下面的图片中看到),这是我的三个本地分支不在同一时间轴上(这肯定是为什么它存在并且不仅仅是垃圾收集的原因)。


Screnshot:Double-double,Git过滤器分支样式

我唯一能想象的是,被删除的提交之一可能是filter-branch实际上所做的单个合并提交删除,并创建了并行的时间轴,因为每个现在未合并的子链都拥有自己的提交副本。 (耸耸肩,我的TARDiS在哪里?)我非常确定我可以解决此问题,尽管我真的很想了解它是如何发生的。

对于疯狂的mergefest-O-RAMA,我因为它已经在我的提交历史中牢牢地确立了自己的地位-每当我靠近它时都会对我造成威胁-似乎并没有真正引起任何非美容性的问题,因为它非常漂亮Tower.app。

#19 楼

更简单的方法


安装git splits。我基于jkeating的解决方案将其创建为git扩展。
将目录拆分为本地分支
#change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2
在某处创建一个空的仓库。我们假设我们在GitHub上创建了一个名为xyz的空仓库,其路径为:git@github.com:simpliwp/xyz.git
推送到新仓库。
#add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master
将新创建的远程仓库克隆到新的本地目录中 #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


评论


与“简易方式”相比,此方法的优点是已经为新的存储库设置了远程服务器,因此您可以立即添加子树。实际上,这种方式对我来说似乎更容易(即使没有git split)

– M.M
2015年5月12日下午5:58

提议AndrewD发布此解决方案。我对他的仓库进行了分叉,使其可以在OSX上运行(github.com/ricardoespsanto/git-splits),如果这对其他人有用的话

–ricardoespsanto
18-2-9在13:33



#20 楼

在垃圾回收之前,您可能需要类似“ git reflog expire --expire = now --all”之类的东西,才能真正清除文件。 git filter-branch只会删除历史记录中的引用,但不会删除保存数据的reflog条目。当然,请先进行测试。

尽管初始条件有所不同,但这样做却使磁盘使用率急剧下降。也许--subdirectory-filter否定了这种需求,但我对此表示怀疑。

#21 楼

在https://github.com/vangorra/git_split

上查看git_split项目。将git目录转入自己位置的自己的存储库中。没有子树可笑的事。该脚本将在您的git存储库中使用一个现有目录,并将该目录转换为自己的独立存储库。在此过程中,它将复制您提供的目录的整个更改历史记录。

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.


#22 楼

将其放入您的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'


#23 楼

我确定git子树一切都很好,但我想移动的git托管代码的子目录全部在eclipse中。
因此,如果您使用egit,那很容易。
您要移动的项目,并团队->断开连接,然后团队->将其共享到新位置。默认情况下,它尝试使用旧的仓库位置,但是您可以取消选中现有用途,然后选择新位置来移动它。
所有冰雹。

评论


子树的“精妙”部分是您子目录的历史记录。如果您不需要历史记录,那么走痛苦的简单方法就是路。

– pglezen
16年2月11日在19:55

#24 楼

您可以轻松尝试https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

我。我在上述步骤中遇到的问题是此命令中的


git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
如果由于保护问题而提交时最后一步失败,则BRANCH-NAME是master
-https://docs.gitlab.com/ee/user/project/protected_branches.html


#25 楼

我找到了一个非常简单的解决方案,
它的想法是复制存储库,然后删除不必要的部分。
这是它的工作方式:

1)克隆一个存储库想拆分

git clone git@git.thehost.io:testrepo/test.git


2)移至git文件夹

cd test/


2)删除不必要的文件夹并提交

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'


3)使用BFG删除不必要的文件夹表单历史记录

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive



对于乘法文件夹,可以使用逗号

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git



4)检查历史记录是否不包含您刚刚删除的文件/文件夹

git log --diff-filter=D --summary | grep delete


5)现在您有了没有ABC的干净存储库,
所以只需将其推入新的来源

remote add origin git@github.com:username/new_repo
git push -u origin master


/>就这样。您可以重复步骤以获取另一个存储库,

仅删除XY1,XY2并在步骤3上重命名XYZ-> ABC

评论


几乎是完美的……但是您忘记了“ git filter-branch --prune-empty”来删除所有现在为空的旧提交。在推向原产地大师之前要做!

– ZettaCircl
19年5月24日在12:02

如果您犯了错误,并且在删除旧的空提交后仍然想“重推”,请执行:“ git push -u origin master --force-with-lease”

– ZettaCircl
19年5月24日,12:10