将子目录分离(移动)到单独的Git存储库中
我有一个包含许多子目录的存储库。现在我发现其中一个子目录与另一个子目录无关,应该分离到单独的存储库将子目录分离(移动)到单独的Git存储库中,git,git-subtree,git-filter-branch,Git,Git Subtree,Git Filter Branch,我有一个包含许多子目录的存储库。现在我发现其中一个子目录与另一个子目录无关,应该分离到单独的存储库 mkdir ~/btoa/ && cd ~/btoa/ git init git pull ~/node-browser-compat btoa-only 如何在保持子目录中文件的历史记录的同时执行此操作 我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在签出较旧版本等时为我提供完整的树。这可能是可以接受的,但我更希望能够假装这两个存储库没有共享的历史 我只想说清楚
mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only
如何在保持子目录中文件的历史记录的同时执行此操作
我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在签出较旧版本等时为我提供完整的树。这可能是可以接受的,但我更希望能够假装这两个存储库没有共享的历史
我只想说清楚,我有以下结构:
XYZ/
.git/
XY1/
ABC/
XY2/
但我希望这样:
XYZ/
.git/
XY1/
XY2/
ABC/
.git/
ABC/
更新:这个过程非常常见,git团队使用一个新的工具,
git子树
使它变得更加简单。请看这里:
您希望克隆存储库,然后使用
git filter branch
标记除您希望在新repo中的子目录之外的所有内容
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
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
警告:所有标记的Ref'refs/tags/v0.1'都没有改变
(因为它们都与子项目无关);此外,移除此类标签后,将回收更多空间。显然,git过滤器分支
应该能够重写其他标记,但我无法验证这一点。如果要删除所有标记,请使用git tag-l | xargs git tag-d
--标记名筛选器cat--prune empty
,以删除空提交并重写标记(请注意,这将不得不去掉它们的签名):
或者,仅重写头部分支并忽略标记和其他分支:
cd /ABC
for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
git remote rm origin
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
git filter branch
确实应该添加参数--all
。是的,那真的是--空间--全部
。这需要是命令的最后一个参数。正如Matli所发现的那样,这会将项目分支和标记保留在新回购协议中
编辑:合并了下面评论中的各种建议,以确保存储库确实缩小了(以前不总是这样)。创建一个包含/ABC的新存储库,但不会从/XYZ中删除/ABC。以下命令将从/XYZ中删除/ABC:
git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD
当然,首先在“clone--no hardlinks”存储库中测试它,然后使用reset、gc和prune命令Paul list进行测试。在垃圾收集之前,您可能需要类似“git reflog expire--expire=now--all”的命令来实际清除文件。git filter分支只删除历史记录中的引用,但不删除保存数据的reflog条目。当然,先测试一下 在这样做的过程中,我的磁盘使用率急剧下降,尽管我的初始条件有所不同。也许——子目录过滤器否定了这种需要,但我对此表示怀疑。为了补充,我发现要最终恢复空间,我必须将HEAD推到一个干净的存储库中,这样可以减小.git/objects/pack目录的大小 i、 e $mkdir…ABC.git $cd…ABC.git $git init—裸 在gc修剪之后,还要执行以下操作: $ git push ...ABC.git HEAD $git推送…ABC.git头 那你就可以了 $ git clone ...ABC.git $git克隆…ABC.git 并且ABC/.git的大小减小了 实际上,push to clean存储库不需要一些耗时的步骤(例如git gc),例如: $ git clone --no-hardlinks /XYZ /ABC $ git filter-branch --subdirectory-filter ABC HEAD $ git reset --hard $ git push ...ABC.git HEAD $git克隆--无硬链接/XYZ/ABC $git筛选器分支--子目录筛选器ABC头 $git重置--硬 $git推送…ABC.git头
我发现,为了正确地从新存储库中删除旧的历史记录,您必须在
过滤器分支
步骤之后再做一些工作
git clone --no-hardlinks foo bar; cd bar
git filter-branch --subdirectory-filter subdir/you/want
git remote rm origin
git update-ref -d refs/original/refs/heads/master
git reflog expire --expire=now --all
git repack -ad
在.更新:git子树模块非常有用,以至于git团队将其拉入核心,并将其制作成
git子树
。请看这里:
git子树在这方面可能很有用
(已弃用)
使用此筛选命令删除子目录,同时保留标记和分支:
cd /ABC
for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
git remote rm origin
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
git过滤器分支——索引过滤器\
“git rm-r-f--cached--ignore unmatch DIR--prune empty”\
--标签名称过滤器cat--all
编辑:添加了Bash脚本
这里给出的答案只对我起了部分作用;缓存中保留了许多大文件。最终起作用的(在freenode上的#git中运行数小时后):
对于以前的解决方案,存储库大小约为100 MB。这一个使它下降到1.7MB。也许这对某人有帮助:)
下面的bash脚本自动执行
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'
cd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>
mkdir ~/<new-repo> && cd ~/<new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
git remote add origin <git@github.com:user/new-repo.git>
git push -u origin master
git rm -rf <name-of-folder>
tree ~/node-browser-compat
node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator
cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only
mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only
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
git rm -rf btoa
brew install git
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
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 filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
git log -- <name-of-folder> # should show nothing
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
git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
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
mkdir <new-repo>
pushd <new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
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_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.
ABC/
/move_this_dir # did some work here, then renamed it to
ABC/
/move_this_dir_renamed
git clone git@git.thehost.io:testrepo/test.git
cd test/
rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'
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
git log --diff-filter=D --summary | grep delete
remote add origin git@github.com:username/new_repo
git push -u origin master
# 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
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