如何分割一个Git仓库,同时保留子目录?

我想要的是类似于这个问题 。 但是,我想要拆分成一个单独的回购目录保持该回购目录中的子目录:

我有这个:

foo/ .git/ bar/ baz/ qux/ 

我想把它分成两个完全独立的仓库:

 foo/ .git/ bar/ baz/ quux/ .git/ qux/ # Note: still a subdirectory 

如何在git中做到这一点?

我可以使用这个答案的方法,如果有一些方法可以将所有新的repo的内容转移到一个子目录,整个历史。

你确实可以使用子目录filter,然后是索引filter,将内容放回到子目录中,但是为什么还要使用索引filter呢?

以下是手册页中的一个示例:

 git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD 

这只是删除一个文件名; 你想要做的是删除一切,但一个给定的子目录。 如果你想保持谨慎的话,你可以明确地列出每条path,但是如果你想全押,你可以这样做:

 git filter-branch --index-filter 'git ls-tree -z --name-only --full-tree $GIT_COMMIT | grep -zv "^directory-to-keep$" | xargs -0 git rm --cached -r' -- --all 

我想可能有一个更优雅的方式; 如果有人有东西,请build议!

关于这个命令的一些注意事项:

  • filter-branch内部将GIT_COMMIT设置为当前提交SHA1
  • 我不希望 – --full-tree是必要的,但显然filter-branch运行从.git-rewrite/t目录而不是repo的最高级别的索引filter。
  • grep可能是矫枉过正,但我​​不认为这是一个速度问题。
  • – 所有这些都适用于所有的裁判; 我想你真的想这样做。 ( --从过滤分支选项中分离出来)
  • -z-0告诉ls-tree,grep和xargs使用NUL终止来处理文件名中的空格。

编辑,稍后:托马斯有用地build议一种方法来删除现在空的提交,但现在已经过时了。 看看编辑历史,如果你有一个旧版本的git,但是对于现代的git,你所需要做的就是添加这个选项:

 --prune-empty 

这将删除索引filter应用后的所有提交。

我想做一个类似的事情,但是由于我想保留的文件列表相当长,使用无数的greps做这个没有任何意义。 我写了一个脚本,从文件中读取文件列表:

 #!/bin/bash # usage: # git filter-branch --prune-empty --index-filter \ # 'this-script file-with-list-of-files-to-be-kept' -- --all if [ -z $1 ]; then echo "Too few arguments." echo "Please specify an absolute path to the file" echo "which contains the list of files that should" echo "remain in the repository after filtering." exit 1 fi # save a list of files present in the commit # which is currently being modified. git ls-tree -r --name-only --full-tree $GIT_COMMIT > files.txt # delete all files that shouldn't be removed while read string; do grep -v "$string" files.txt > files.txt.temp mv -f files.txt.temp files.txt done < $1 # remove unwanted files (ie everything that remained in the list). # warning: 'git rm' will exit with non-zero status if it gets # an invalid (non-existent) filename OR if it gets no arguments. # If something exits with non-zero status, filter-branch will abort. # That's why we have to check carefully what is passed to git rm. if [ "$(cat files.txt)" != "" ]; then cat files.txt | \ # enclose filenames in "" in case they contain spaces sed -e 's/^/"/g' -e 's/$/"/g' | \ xargs git rm --cached --quiet fi 

令人惊讶的是,这比我最初预期的要多得多,所以我决定把它发布在这里。

这是我自己解决这个问题时最终做的:

 git filter-branch --index-filter \ 'git ls-tree --name-only --full-tree $GIT_COMMIT | \ grep -v "^directory-to-keep$" | \ sed -e "s/^/\"/g" -e "s/$/\"/g" | \ xargs git rm --cached -r -f --ignore-unmatch \ ' \ --prune-empty -- --all 

该解决scheme是基于Jefromi的回答,并将Detach(移动)子目录放入单独的Git存储库,并在此处添加许多注释。

Jefromi的解决scheme之所以不适合我,是因为我的回购站中有文件和文件夹,其名称中包含特殊字符(主要是空格)。 另外git rm抱怨不匹配的文件(用--ignore-unmatch解决)。

您可以保持过滤不可知的目录不在回购的根目录或移动:

 grep --invert-match "^.*directory-to-keep$" 

最后,您可以使用它来过滤出一个固定的文件或目录子集:

 egrep --invert-match "^(.*file-or-directory-to-keep-1$|.*file-or-directory-to-keep-2$|…)" 

事后清理,你可以使用这些命令:

 $ git reset --hard $ git show-ref refs/original/* --hash | xargs -n 1 git update-ref -d $ git reflog expire --expire=now --all $ git gc --aggressive --prune=now 

更清洁的方法:

 git filter-branch --index-filter ' git read-tree --empty git reset $GIT_COMMIT path/to/dir ' \ -- --all -- path/to/dir 

或坚持只是核心命令,在子目录git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir复位。

在rev-list args中指定path/to/dir会尽早修剪,使用这种便宜的filter并不重要,但最好避免浪费精力。