mat*_*tli 1712 git git-filter-branch git-subtree
我有一个Git存储库,其中包含许多子目录.现在我发现其中一个子目录与另一个子目录无关,应该分离到一个单独的存储库.
如何在将文件的历史记录保存在子目录中的同时执行此操作?
我想我可以制作一个克隆并删除每个克隆的不需要的部分,但我想这会给我一个完整的树,当检查旧版本等.这可能是可以接受的,但我宁愿能够假装两个存储库没有共享历史记录.
为了说清楚,我有以下结构:
XYZ/
.git/
XY1/
ABC/
XY2/
Run Code Online (Sandbox Code Playgroud)
但我想这样做:
XYZ/
.git/
XY1/
XY2/
ABC/
.git/
ABC/
Run Code Online (Sandbox Code Playgroud)
Coo*_*J86 1259
事实证明,这是一个非常普遍和有用的做法,git的霸主使它变得非常容易,但你必须有一个更新版本的git(> = 1.7.11 2012年5月).有关如何安装最新git的信息,请参阅附录.此外,下面的演练中有一个真实的例子.
准备旧的回购
pushd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>
popd
Run Code Online (Sandbox Code Playgroud)
注意: <name-of-folder>
不得包含前导或尾随字符.例如,名为subproject
MUST 的文件夹必须作为subproject
NOT 传递./subproject/
Windows用户注意事项:当文件夹深度> 1时,<name-of-folder>
必须具有*nix样式文件夹分隔符(/).例如,名为path1\path2\subproject
MUST 的文件夹必须作为path1/path2/subproject
创建新的仓库
mkdir <new-repo>
pushd <new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
Run Code Online (Sandbox Code Playgroud)将新的回购链接链接到Github或任何地方
git remote add origin <git@github.com:my-user/new-repo.git>
git push origin -u master
Run Code Online (Sandbox Code Playgroud)如果需要,清理
popd # get out of <new-repo>
pushd <big-repo>
git rm -rf <name-of-folder>
Run Code Online (Sandbox Code Playgroud)
注意:这将保留存储库中的所有历史引用.如果您确实担心已提交密码或需要减小文件夹的文件大小,请参阅下面的附录.git
.
...
这些步骤与上述步骤相同,但遵循我的存储库的确切步骤而不是使用<meta-named-things>
.
这是我在节点中实现JavaScript浏览器模块的项目:
tree ~/Code/node-browser-compat
node-browser-compat
??? ArrayBuffer
??? Audio
??? Blob
??? FormData
??? atob
??? btoa
??? location
??? navigator
Run Code Online (Sandbox Code Playgroud)
我想将一个文件夹拆分btoa
成一个单独的git存储库
pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd
Run Code Online (Sandbox Code Playgroud)
我现在有一个新的分支,btoa-only
只有提交btoa
,我想创建一个新的存储库.
mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only
Run Code Online (Sandbox Code Playgroud)
接下来我在Github或bitbucket上创建一个新的repo,或者其他什么并添加它是origin
(顺便说一句,"origin"只是一个约定,不是命令的一部分 - 你可以称之为"远程服务器"或任何你喜欢的)
git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master
Run Code Online (Sandbox Code Playgroud)
愉快的一天!
注意:如果你创建了一个带有回购协议README.md
,.gitignore
并且LICENSE
,你需要先拉:
git pull origin -u master
git push origin -u master
Run Code Online (Sandbox Code Playgroud)
最后,我想从更大的仓库中删除该文件夹
git rm -rf btoa
Run Code Online (Sandbox Code Playgroud)
...
要获取最新版本的git:
brew install git
Run Code Online (Sandbox Code Playgroud)
要获得OS X的酿造:
sudo apt-get update
sudo apt-get install git
git --version
Run Code Online (Sandbox Code Playgroud)
如果这不起作用(你有一个非常旧版本的ubuntu),试试吧
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
Run Code Online (Sandbox Code Playgroud)
如果仍然无效,请尝试
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
Run Code Online (Sandbox Code Playgroud)
感谢rui.araujo的评论.
默认情况下从git中删除文件实际上并没有从git中删除它们,它只是提交它们不再存在.如果要实际删除历史引用(即您已提交密码),则需要执行以下操作:
git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
Run Code Online (Sandbox Code Playgroud)
之后,您可以检查您的文件或文件夹根本不再显示在git历史记录中
git log -- <name-of-folder> # should show nothing
Run Code Online (Sandbox Code Playgroud)
但是,您无法"删除"删除到github等.如果你尝试过你会得到一个错误,你必须git pull
在你能做到之前git push
- 然后你就会回到你历史上的所有事情.
因此,如果你想从"origin"中删除历史记录 - 意思是从github,bitbucket等删除它 - 你需要删除repo并重新推送repo的修剪副本.但是等等 - 还有更多! - 如果你真的担心要删除密码或类似的东西,你需要修剪备份(见下文).
.git
小前面提到的删除历史记录命令仍然留下了一堆备份文件 - 因为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
Run Code Online (Sandbox Code Playgroud)
也就是说,我建议不要执行这些步骤,除非你知道你需要 - 以防万一你修剪了错误的子目录,你知道吗?推送回购时,不应克隆备份文件,它们只是在本地副本中.
Pau*_*aul 1203
更新:这个过程非常普遍,git团队使用新工具使其变得更加简单git subtree
.请参见此处:将(移动)子目录分离到单独的Git存储库中
您希望克隆您的存储库,然后使用git filter-branch
标记除新回购中所需的子目录之外的所有内容进行垃圾回收.
要克隆本地存储库:
git clone /XYZ /ABC
Run Code Online (Sandbox Code Playgroud)
(注意:存储库将使用硬链接进行克隆,但这不是问题,因为硬链接文件本身不会被修改 - 将创建新的文件.)
现在,让我们保留我们想要重写的有趣分支,然后删除原点以避免在那里推送并确保原始提交不会被原点引用:
cd /ABC
for i in branch1 br2 br3; do git branch -t $i origin/$i; done
git remote rm origin
Run Code Online (Sandbox Code Playgroud)
或者对于所有远程分支:
cd /ABC
for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
git remote rm origin
Run Code Online (Sandbox Code Playgroud)现在您可能还想删除与子项目无关的标记; 您也可以稍后再这样做,但您可能需要再次修剪您的仓库.我没有这样做,得到了WARNING: Ref 'refs/tags/v0.1' is unchanged
所有标签(因为它们都与子项目无关); 此外,在移除此类标签后,将回收更多空间.显然git filter-branch
应该能够重写其他标签,但我无法验证这一点.如果要删除所有标签,请使用git tag -l | xargs git tag -d
.
然后使用filter-branch和reset来排除其他文件,这样就可以对它们进行修剪.我们还添加--tag-name-filter cat --prune-empty
删除空提交并重写标记(请注意,这将删除其签名):
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
Run Code Online (Sandbox Code Playgroud)
或者,只重写HEAD分支并忽略标签和其他分支:
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
Run Code Online (Sandbox Code Playgroud)然后删除备份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
Run Code Online (Sandbox Code Playgroud)
现在你有一个ABC子目录的本地git存储库,其中保留了所有历史记录.
注意:对于大多数用途,git filter-branch
确实应该添加参数-- --all
.是的,这是真的--space-- all
.这需要是命令的最后一个参数.正如Matli发现的那样,这使得项目分支和标签包含在新的仓库中.
编辑:合并了以下评论中的各种建议,以确保,例如,存储库实际上是缩小的(以前并非总是如此).
pgs*_*pgs 132
Paul的回答创建了一个包含/ ABC的新存储库,但是没有从/ XYZ中删除/ ABC.以下命令将从/ XYZ中删除/ ABC:
git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD
Run Code Online (Sandbox Code Playgroud)
当然,首先在'clone --no-hardlinks'存储库中测试它,然后使用Paul列出的reset,gc和prune命令进行测试.
Jos*_*Lee 94
我发现为了从新存储库中正确删除旧历史记录,您必须在该filter-branch
步骤之后再做一些工作.
做克隆和过滤器:
git clone --no-hardlinks foo bar; cd bar
git filter-branch --subdirectory-filter subdir/you/want
Run Code Online (Sandbox Code Playgroud)删除对旧历史的每个引用."origin"跟踪你的克隆,"original"是filter-branch保存旧东西的地方:
git remote rm origin
git update-ref -d refs/original/refs/heads/master
git reflog expire --expire=now --all
Run Code Online (Sandbox Code Playgroud)即使是现在,你的历史可能会陷入fsck不会触及的包文件中.撕碎它,创建一个新的packfile并删除未使用的对象:
git repack -ad
Run Code Online (Sandbox Code Playgroud)Sim*_*ter 39
编辑:添加了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
Run Code Online (Sandbox Code Playgroud)
使用以前的解决方案,存储库大小约为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
Run Code Online (Sandbox Code Playgroud)
jer*_*own 26
这不再是那么复杂,您可以在您的repo克隆上使用git filter-branch命令来剔除您不想要的子目录,然后推送到新的远程.
git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
Run Code Online (Sandbox Code Playgroud)
lpe*_*son 23
当git filter-branch
使用较新版本的git
(2.22+
也许?)运行时,它说要使用这个新工具git-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
Run Code Online (Sandbox Code Playgroud)
假设: * 远程 XYZ 存储库在推送之前是新的并且是空的
就我而言,我还想移动几个目录以获得更一致的结构。最初,我运行了那个简单的filter-repo
命令,然后是git mv dir-to-rename
,但我发现使用该--path-rename
选项可以获得稍微“更好”的历史记录。5 hours ago
我现在看到last year
(在 GitHub UI 中),而不是在新存储库中看到最后修改的移动文件,它与原始存储库中的修改时间相匹配。
代替...
git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3 # which updates last modification time
Run Code Online (Sandbox Code Playgroud)
我最终跑了...
git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Run Code Online (Sandbox Code Playgroud)
笔记:
git filter-repo --subdirectory-filter dir-matching-new-repo-name
)。该命令正确地将该子目录转换为复制的本地存储库的根目录,但它也导致只有创建子目录所花费的三个提交的历史记录。(我没有意识到--path
可以多次指定;因此,无需在源存储库中创建子目录。)因为有人在我注意到我未能继续执行时已提交到源存储库历史,我只是git reset commit-before-subdir-move --hard
在clone
命令之后使用,并添加--force
到filter-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
Run Code Online (Sandbox Code Playgroud)
git
,但最终我克隆了git-filter-repo并将它的符号链接到$(git --exec-path)
:git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3 # which updates last modification time
Run Code Online (Sandbox Code Playgroud)
D W*_*D W 19
更新:git-subtree模块非常有用,git团队将其拉入核心并成功实现git subtree
.请参见此处:将(移动)子目录分离到单独的Git存储库中
git-subtree可能对此有用
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/
Ant*_* O. 19
这是对CoolAJ86的"The Easy Way™"答案的一个小修改,以便将多个子文件夹(比如说sub1
和sub2
)拆分成一个新的git存储库.
准备旧的回购
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
Run Code Online (Sandbox Code Playgroud)
注意: <name-of-folder>
不得包含前导或尾随字符.例如,名为subproject
MUST 的文件夹必须作为subproject
NOT 传递./subproject/
Windows用户注意事项:当文件夹深度> 1时,<name-of-folder>
必须具有*nix样式文件夹分隔符(/).例如,名为path1\path2\subproject
MUST 的文件夹必须作为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>
Run Code Online (Sandbox Code Playgroud)将新的回购链接链接到Github或任何地方
git remote add origin <git@github.com:my-user/new-repo.git>
git push origin -u master
Run Code Online (Sandbox Code Playgroud)如果需要,清理
popd # get out of <new-repo>
pushd <big-repo>
git rm -rf <name-of-folder>
Run Code Online (Sandbox Code Playgroud)
注意:这会在存储库中保留所有历史引用.如果您确实担心提交密码或需要减小文件.git
夹的文件大小,请参阅原始答案中的附录.
MM.*_*MM. 12
原始问题想要XYZ/ABC /(*文件)成为ABC/ABC /(*文件).在为我自己的代码实现接受的答案后,我注意到它实际上将XYZ/ABC /(*文件)更改为ABC /(*文件).filter-branch手册页甚至说,
结果将包含该目录(并且仅包含该目录)作为其项目根目录."
换句话说,它将顶级文件夹"升级"一级.这是一个重要的区别,因为,例如,在我的历史中,我已经重命名了一个顶级文件夹.通过将文件夹"提升"到一个级别,git在我进行重命名的提交时失去连续性.
我对问题的回答是制作2个存储库副本并手动删除要保留在每个存储库中的文件夹.该手册页支持我:
[...]如果简单的单一提交足以解决您的问题,请避免使用[此命令]
小智 7
为了增加Paul的答案,我发现要最终恢复空间,我必须将HEAD推送到一个干净的存储库,并减少.git/objects/pack目录的大小.
即
$ 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
似乎这里的大多数(所有?)答案都依赖于某种形式的git filter-branch --subdirectory-filter
和类似的.这可能"大多数时间"工作但是对于某些情况,例如重命名文件夹的情况,例如:
ABC/
/move_this_dir # did some work here, then renamed it to
ABC/
/move_this_dir_renamed
Run Code Online (Sandbox Code Playgroud)
如果你做一个普通的Git过滤器的风格,以提取"move_me_renamed"你将失去的文件变化的历史,从后发生时它最初move_this_dir(REF).
因此,看来真正保留所有变更历史的唯一方法(如果你的是这样的情况),实质上是复制存储库(创建一个新的存储库,设置它作为原点),然后核对其他一切并将子目录重命名为父目录,如下所示:
git branch -a
git checkout --track origin/branchABC
cp -r oldmultimod simple
cd simple
git rm otherModule1 other2 other3
git mv moduleSubdir1/* .
rmdir moduleSubdir1
git status
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文件夹中的任何空间节省任何空间,但它会保留这些文件的所有更改历史记录,甚至是重命名文件.如果没有"很多"历史丢失等等,这可能不值得.但至少你保证不会丢失旧的提交!
我推荐GitHub's guide to splitting subfolders into a new repository。这些步骤类似于保罗的回答,但我发现他们的说明更容易理解。
我修改了说明,以便他们申请本地存储库,而不是托管在 GitHub 上的存储库。
将子文件夹拆分为新的存储库
打开 Git Bash。
将当前工作目录更改为要创建新存储库的位置。
克隆包含子文件夹的存储库。
Run Code Online (Sandbox Code Playgroud)git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
- 将当前工作目录更改为您克隆的存储库。
Run Code Online (Sandbox Code Playgroud)cd REPOSITORY-NAME
- 要从存储库中的其余文件中过滤掉子文件夹,请运行
git filter-branch
,并提供以下信息:
FOLDER-NAME
:项目中要从中创建单独存储库的文件夹。
- 提示:Windows 用户应使用
/
分隔文件夹。BRANCH-NAME
:当前项目的默认分支,例如,master
或gh-pages
.Run Code Online (Sandbox Code Playgroud)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 的标准解决方案都非常慢。如果您有一个小型存储库,那么这可能不是问题,它适合我。我编写了另一个基于 libgit2 的 git 过滤程序,它第一步为主存储库的每个过滤创建分支,然后将这些分支推送到清理存储库作为下一步。在我的存储库(500Mb 100000 次提交)中,标准的 git filter-branch 方法需要几天时间。我的程序需要几分钟来做同样的过滤。
它有一个很棒的名字 git_filter 并且住在这里:
https://github.com/slobobaby/git_filter
在 GitHub 上。
我希望它对某人有用。
归档时间: |
|
查看次数: |
245081 次 |
最近记录: |