git - 分离子目录到Git存储库

  显示原文与译文双语对照的内容

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

如何在保存子目录中的文件历史时执行这里操作?

我想我可以创建一个克隆并删除每个克隆的不需要的部分,但我想这将给我一个完整的树,当签出一个旧的版本 等等 时,这可能是可以接受的,但我希望两个仓库没有共享历史。

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


XYZ/
. git/
 XY1/
 ABC/
 XY2/

但我想要这样做:


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

时间:

更新: 这个过程很常见,git团队使用一个新工具使得它变得更加简单,git subtree 。 参见这里:将子目录分离到单独的Git存储库列表中


你想克隆你的存储库,然后使用 git filter-branch 来标记你想要在新仓库中的子目录以外的所有内容。

  1. 克隆本地存储库:

    
    git clone/XYZ/ABC
    
    

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

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

    
    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
    
    
  3. 现在你可能想要删除与子工程没有关系的标记;你也可以后再做,但是你可能需要再次修剪你的存储库。 我没有这样做,得到了一个 WARNING: Ref 'refs/tags/v0.1' is unchanged 对于所有标记( 因为它们都与子项目无关) ;另外,删除此类标记后,将回收更多的空间。 显然 git filter-branch 应该能够重写其他标记,但我无法验证。 如果要删除所有标记,请使用 git tag -l | xargs git tag -d

  4. 然后使用filter-branch并重置以排除其他文件,这样它们就可以被删除。 我们也添加 --tag-name-filter cat --prune-empty 要删除空提交并重写标记( 注意,这必须去掉他们的签名):

    
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    
    

    或者,只重写头部分支和忽略标记和其他分支:

    
    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
    
  5. 然后删除备份 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
    
    

    现在你拥有了一个sub-directory的本地git仓库,它的历史保留了所有历史。

注意:对于大多数用法,git filter-branch 实际上应该有添加的参数 -- --all 。 是的,真的是破折号破折号破折号 all 。 这需要是命令的最后一个参数。 就像Matli发现的那样,这将保留新存储库中包含的项目分支和标记。

编辑:来自以下评论的各种建议被合并,以确保存储库实际上已经收缩( 以前并不总是这样) 。

事实证明这是一个常见的和有用的实践,overlords的使得它非常容易,但是你必须有一个新版的git (> = 1.7.11 2012年05月 ) 。 之处在于,如何来安装最新的git,看到附录. 在演练 below,也,有一个真实示例.

  1. 准备旧的仓库

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

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

    于 Windows 相关 users, note: 当文件夹深度为> 1时,<name-of-folder> 必须具有 *nix 样式文件夹分隔符(/) 。 例如名为 path1path2subproject的文件夹必须作为 path1/path2/subproject 传递

  2. 创建新的仓库

    
    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
    
  3. 将新的仓库链接到Github或者任何地方

    
    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
    
  4. 清洗,如果需要

    
    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    
    

    注意: 这样就会根据以往的全部历史引用在下面的在附录 repository.See 如果你实际上是担心遇到提交一个密码或者需要降低的文件尺寸 .git 文件夹中。

漫游

使用上面的步骤是相同的这些都是为我的存储库而不是 of.,但以下我的确切步骤

以下是我在node中实现JavaScript浏览器模块的一个项目:


tree ~/Code/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

我想把一个单独的文件夹 btoa 分成一个单独的git仓库


pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

我现在有了一个新的分支 btoa-only,它只有 btoa的提交,我想创建一个新的仓库。


mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

接下来我在Github或者bitbucket上创建一个新的仓库,或者任何东西,添加它是 origin ( 顺便说一句,""只是一个惯例,不是命令的一部分- 你可以称之为"remote-server"或者你喜欢的东西


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

快乐日 !

注意: 如果你创建了一个回购带有 README.md.gitignoreLICENSE,你将需要拉先-


git pull origin -u master
git push origin -u master

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


git rm -rf btoa

附录

OS X 上最新的git

获取最新版本的git:

 
brew install git

 

要获取 OS X的brew:

http://brew.sh

最新的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

感谢 rui.araujo的评论。

正在清除你的历史记录

默认情况下,从git删除文件并不会真正删除 git,它只是提交它们不再存在。 如果你想实际删除历史引用( 例如 。 你已经提交了一个密码,你需要这样做:


git filter-branch --tree-filter 'rm -rf <name-of-folder>' HEAD

之后,你可以检查你的文件或者文件夹是否不再显示在git历史中


git log -S<name-of-folder> # should show nothing

但是,你不能"推"删除到 gitub 和类似的东西。 在你history,如果你试试,你会收到一个错误消息,你会固定使用不必 git pull 可以 git push - 然后就可以继续到之前有

所以如果你想从"原点"删除历史- 意味着从github删除它,bitbucket等- 你需要删除仓库和re-push一个已经修剪的拷贝。 但是等等- 有多个 ! - 如果你真的担心删除密码或者类似的东西,你需要修剪备份( 见下文) 。

使 .git 变小

上面提到的删除历史命令仍然会留下一堆备份文件- 因为git在帮助你避免意外销毁你的仓库的时候 is 。 它最终会在几天和月内删除孤立的文件,但如果你意识到你无意中删除了你不想删除的东西,那么它会把它们留在那里。

所以如果你真的想清倒废纸篓到减少克隆的大小从仓库立马就有要做所有这些事情确实很怪异的本领


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

也就是说,我建议别执行这些步骤,除非你知道你需要- 以防万一你有修剪错误的子目录中,y'知道? 当你推仓库时,备份文件不应该被克隆,它们会在你的本地副本中。

信用

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


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

当然,首先在'克隆 --no-hardlinks'知识库中测试它,然后使用 reset,gc和prune命令来跟踪它。

我发现为了正确地从新存储库中删除旧的历史,你必须在 filter-branch 步骤之后再做一些工作。

  1. 执行克隆和筛选器:

    
    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
    
  2. 删除对旧历史的每个引用。 "原始"一直在跟踪你的克隆人"原始"是filter-branch保存旧内容的地方:

    
    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
    
  3. 即使现在,你的历史可能会陷入一个packfile不会触摸的。 将它的撕成碎片,创建一个新的packfile并删除未使用的对象:

     
    git repack -ad
    
     

手册中为 filter-branch, 有这里. 它的用途说明

编辑:添加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

更新: 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/

最初的问题在实现了我自己的代码的接受答案后,希望 XYZ/ABC/(*files) 成为 ABC/ABC/(*files).,我注意到它实际上将 XYZ/ABC/(*files) 更改为 abc/( *files ) 。 filter-branch手册页甚至说

结果将包含目录( 而且只有这样) 作为它的项目根 。"

换句话说,它提升了top-level文件夹"向上"一级。 这是一个重要的区别,例如在我的历史中,我重命名了一个top-level文件夹。 通过提升文件夹"向上"一级,git在提交时失去了连续性,我在这里进行了重命名。

I lost contiuity after filter-branch

我对这个问题的回答是制作 2副本的知识库,并手动删除你想要保存在每个。 手册页支持我:

[...] 如果简单的单一提交足以解决你的问题,请避免使用 [this command]

要添加到的保罗 回答时,我发现到最终的空间我也要推头恢复到一个干净的存储库和修剪下来的大小. git/objects/pack 目录。

等等

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

在gc修剪之后,也可以执行以下操作:

$ git push.. .ABC.git HEAD

然后你可以做

$ git clone.. .ABC.git

/。git的大小减少了

实际上,一些耗时的步骤( 例如。 在强制清理知识库时不需要 git gc,i.e.:

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

这不再是如此复杂,以至于我们可以只使用建议最新filter-branch上命令来克隆一个你回购来过滤目录你不希望,然后推动到新的远程子目录中。


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

正确的方法如下:

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

GitHub现在甚至有关于这种情况的小文章。

但是一定要克隆你原来的仓库,以便先独立目录( 因为它将删除所有文件和其他目录,你可能需要使用它们) 。

所以你的算法应该是:

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