【问题标题】:Split large Git repository into many smaller ones将大型 Git 存储库拆分为许多较小的存储库
【发布时间】:2011-04-24 01:11:49
【问题描述】:

在成功将 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/

我已尝试按照上一个问题中的说明进行操作,但在尝试将多个目录放入单独的存储库 (Detach (move) subdirectory into separate Git repository) 时,它并不适合。

【问题讨论】:

  • 如果您对答案感到满意,请将其标记为已接受。
  • 对于希望将多个(嵌套)目录拆分为新存储库(而不是希望删除多个目录,这在某些项目上可能更难)的任何人,这个答案对我很有帮助:@987654322 @

标签: git split repository git-filter-branch


【解决方案1】:

这将设置 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' 手册页建议创建重写存储库的新克隆,而不是上面提到的最后一步。
  • 我试过这个,当它试图在最后删除 .git-rewrite 文件夹时出错。
  • -d 为我工作并消除了 --tree-filter 中的 stange 'mv' 故障。
  • 如果第一个提交与排除路径相关(例如DIR_A),您知道如何获取第一个提交吗?
  • 我没有意识到filter-branch 的全部后果。对于那些不知道的人,它会重写历史记录,所以如果你打算在完成后推送 repo,那么提交哈希现在会有所不同,它不会起作用。
【解决方案2】:

您可以使用 git filter-branch --index-filtergit 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

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

【讨论】:

  • : 不是 bash 中的注释字符。你应该改用#
  • @Daenyth, : 是传统的内置命令 (also specified in POSIX)。它包含在 bash 中,但它不是注释。我特别优先使用它而不是 #,因为并非所有 shell 在所有上下文中都将 # 作为评论介绍器(例如,交互式 zsh 没有启用 INTERACTIVE_COMMENTS 选项)。使用: 使整个文本适合粘贴到任何交互式shell 以及保存在脚本文件中。
  • 太棒了!我发现的唯一解决方案可以保持所有分支完好无损
  • 奇怪,对我来说它以 git remote rm origin 停止,它似乎总是返回 1。因此,我将这一行的 && 替换为 ;
  • 很好,$@ 在需要时可用于两个以上的目录。完成后我打电话给git remote add origin $TARGET; git push origin master
【解决方案3】:

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.

【讨论】:

  • 这与上面的filter-branch 答案相同,是吗?如果是这样,我认为它具有重写整个历史的类似问题?
【解决方案4】:

这里有一个 ruby​​ 脚本可以做到这一点。 https://gist.github.com/4341033

【讨论】:

    【解决方案5】:

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

    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 目录做了同样的事情。这给了我两个具有相同历史记录的存储库,直到我删除了不需要的目录。

    【讨论】:

    • 这不会保留提交历史记录。
    • 怎么回事?我仍然拥有所有的历史记录,即使是已删除的文件。
    • 由于您的要求不是 repo A 必须假装 repo B 从未存在,我认为这(留下仅影响 B 的提交记录)是一个合适的解决方案。最好复制一点历史而不是破坏它。
    【解决方案6】:

    虽然在提出问题时 utunbu 的答案是你能得到的最好的答案,但这些天甚至 git 本身也推荐 https://github.com/newren/git-filter-repo

    它的速度要快几个数量级,而且相对来说非常容易使用

    例如在这里你会这样做

    git clone MyHugeRepo/ MyABRepo.tmp/
    cd MyABRepo.tmp
    git filter-repo --path DIR_A/ --path DIR_B/
    

    您可以在https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html#EXAMPLES查看更多示例

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-02
      • 2011-09-12
      • 1970-01-01
      • 2023-03-24
      • 2020-02-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多