将大型Git存储库拆分为许多较小的存储库


86

在将SVN信息库成功转换为Git之后,我现在拥有一个非常大的Git信息库,我想分解为多个较小的信息库并维护历史记录。

因此,有人可以帮助分解可能如下所示的存储库:

MyHugeRepo/
   .git/
   DIR_A/
   DIR_B/
   DIR_1/
   DIR_2/

分为两个如下所示的存储库:

MyABRepo/
   .git
   DIR_A/
   DIR_B/

My12Repo/
   .git
   DIR_1/
   DIR_2/

我已经尝试按照上一个问题中的指示进行操作,但是当尝试将多个目录放入一个单独的仓库(将子目录分离(移动)到单独的Git存储库中)时,它确实不合适。


11
当您对答案感到满意时,请将其标记为已接受。
Ben Fowler 2014年

1
对于希望将多个(嵌套的)目录拆分为新存储库的人(而不是希望删除多个目录,这在某些项目上可能更难),此答案对我很有帮助:stackoverflow.com/a/19957874/164439
thaddeusmt 2015年

Answers:


80

这将设置MyABRepo; 您当然可以类似地做My12Repo。

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 

对.git / refs / original / refs / heads / master的引用仍然存在。您可以使用以下方法将其删除:

cd ..
git clone MyABRepo.tmp MyABRepo

如果一切顺利,则可以删除MyABRepo.tmp。


如果由于某种原因您遇到有关.git-rewrite的错误,则可以尝试以下操作:

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch -d /tmp/git-rewrite.tmp --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 
cd ..
git clone MyABRepo.tmp MyABRepo

这将创建/tmp/git-rewrite.tmp并将其用作临时目录,而不是.git-rewrite。当然,/tmp/git-rewrite.tmp只要您具有写许可权并且该目录尚不存在,就可以用所需的任何路径代替。


“ git filter-branch”手册页建议创建一个新的重写存储库克隆,而不是上面提到的最后一步。
JakubNarębski2010年

我尝试了此操作,并在尝试最后删除.git-rewrite文件夹时遇到错误。
MikeM 2010年

-d <另一个物理磁盘上的路径>对我有用,并消除了--tree-filter中的固定'mv'故障。
Vertigo

您是否有一个想法,如果它与排除的路径(DIR_A例如)有关,如何将其提交出去?
位掩码

1
我没有意识到的全部后果filter-branch。对于那些不知道的人,它会重写历史记录,因此,如果您打算在执行此操作后再推送回购协议,那么提交哈希值现在将有所不同,并且将不起作用。
thaddeusmt 2015年

10

您可以使用git filter-branch --index-filterwithgit rm --cached从原始存储库的克隆/副本中删除不需要的目录。

例如:

trim_repo() { : trim_repo src dst dir-to-trim-out...
  : uses printf %q: needs bash, zsh, or maybe ksh
  git clone "$1" "$2" &&
  (
    cd "$2" &&
    shift 2 &&

    : mirror original branches &&
    git checkout HEAD~0 2>/dev/null &&
    d=$(printf ' %q' "$@") &&
    git for-each-ref --shell --format='
      o=%(refname:short) b=${o#origin/} &&
      if test -n "$b" && test "$b" != HEAD; then 
        git branch --force --no-track "$b" "$o"
      fi
    ' refs/remotes/origin/ | sh -e &&
    git checkout - &&
    git remote rm origin &&

    : do the filtering &&
    git filter-branch \
      --index-filter 'git rm --ignore-unmatch --cached -r -- '"$d" \
      --tag-name-filter cat \
      --prune-empty \
      -- --all
  )
}
trim_repo MyHugeRepo MyABRepo DIR_1 DIR_2
trim_repo MyHugeRepo My12Repo DIR_A DIR_B

您将需要手动删除每个存储库的不需要的分支或标签(例如,如果您有一个功能x-for-AB分支,那么您可能希望将其从“ 12”存储库中删除)。


1
:不是bash中的注释字符。您应该#改用。
2010年

4
@Daenyth:是传统的内置命令(也在POSIX中指定)。它包含在bash中,但不是注释。我#之所以特别优先使用它,是因为并非所有shell#在所有上下文中都用作注释介绍程序(例如,交互式zsh)。未启用INTERACTIVE_COMMENTS选项的)。使用:使整个文本适合粘贴到任何交互式外壳程序中以及保存在脚本文件中。
克里斯·约翰森

1
辉煌!我发现的唯一解决方案可以使所有分支保持完整
pheelicks

奇怪的是,对我来说,它以停止git remote rm origin,它似乎总是返回1。因此,我在这一行替换了&&by ;
kynan 2011年

好的,$ @在需要时可用于两个以上的目录。完成后我打电话git remote add origin $TARGET; git push origin master
Walter

6

git_split项目是一个简单的脚本,可以完全满足您的需求。https://github.com/vangorra/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.


1

感谢您的回答,但最终我只复制了两次存储库,然后从每个存储库中删除了不需要的文件。我将在以后使用过滤器分支来删除所有已删除文件的提交,因为它们已经在其他位置进行了版本控制。

cp -R MyHugeRepo MyABRepo
cp -R MyHugeRepo My12Repo

cd MyABRepo/
rm -Rf DIR_1/ DIR_2/
git add -A
git commit -a

这满足了我的需要。

编辑:当然,在My12Repo中针对A和B目录也做了同样的事情。这给了我两个具有相同历史的存储库,直到我删除了不需要的目录。


1
这不会保留提交历史记录。
丹妮丝

为何如此?我仍然拥有所有历史记录,即使是已删除的文件也是如此。
MikeM 2010年

1
因为您的要求不是回购A必须假装回购B不存在,所以我认为这(保留仅影响B的提交记录)是一个合适的解决方案。复制一点历史总比弄乱它更好。
史蒂夫·克莱
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.