【问题标题】:How to split a git repository while preserving subdirectories?如何在保留子目录的同时拆分 git 存储库?
【发布时间】:2011-02-17 08:08:02
【问题描述】:

我想要的类似于this question。但是,我希望拆分为单独存储库的目录保留该存储库中的子目录:

我有这个:

foo/
  .git/
  bar/
  baz/
  qux/

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

foo/
  .git/
  bar/
  baz/

quux/
  .git/
  qux/  # Note: still a subdirectory

如何在 git 中做到这一点?

如果有某种方法可以将所有新 repo 的内容移动到整个历史记录的子目录中,我可以使用 this answer 中的方法。

【问题讨论】:

    标签: git split git-filter-branch


    【解决方案1】:

    您确实可以使用子目录过滤器,然后使用索引过滤器将内容放回子目录,但是当您可以单独使用索引过滤器时,为什么还要麻烦呢?

    这是手册页中的一个示例:

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

    这只是删除一个文件名;您想要做的是删除除给定子目录之外的所有内容。如果您要谨慎,可以明确列出要删除的每条路径,但如果您只想全押,则可以执行以下操作:

    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
    

    我希望可能有更优雅的方式;如果有人有什么请推荐!

    关于该命令的几点说明:

    • filter-branch 在内部将 GIT_COMMIT 设置为当前提交 SHA1
    • 我没想到 --full-tree 是必要的,但显然 filter-branch 从 .git-rewrite/t 目录而不是 repo 的顶层运行 index-filter。
    • grep 可能有点矫枉过正,但我​​认为这不是速度问题。
    • --all 将此应用于所有参考;我想你真的想要那个。 (-- 将其与过滤器分支选项分开)
    • -z-0 告诉 ls-tree、grep 和 xargs 使用 NUL 终止来处理文件名中的空格。

    编辑,很久以后:Thomas 很有帮助地建议了一种删除现在为空的提交的方法,但它现在已经过时了。如果您有旧版本的 git,请查看编辑历史记录,但使用现代 git,您需要做的就是添加此选项:

    --prune-empty
    

    这将删除应用索引过滤器后所有为空的提交。

    【讨论】:

    • 除了嵌套的单引号(我冒昧地替换了它)之外,这几乎可以完美地工作。唯一的问题是对现在不存在的目录的空提交仍保留在日志中。我使用在github.com/jwiegley/git-scripts/blob/master/… 找到的git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi' "$@" 删除了这些
    • @Thomas:感谢您纠正我的粗心错误!此外,您应该能够在与索引过滤器相同的命令中使用提交过滤器。过滤器按文档中显示的顺序运行;提交过滤器自然位于修改提交内容的过滤器之后。您可能还想使用--remap-to-ancestor,这将导致指向跳过提交的引用被移动到最近的祖先而不是排除它们。
    • @Jefromi:index-filter 参数应该更容易表达为git rm -r -f --cached --ignore-unmatch $(ls !(directory-to-keep)),请参阅我的答案stackoverflow.com/a/8079852/396967stackoverflow.com/a/7849648/396967
    • 如果您的文件名有空格,那么您可以在ls-tree| grep 之间添加| tr "\n" "\0"(将换行符变为NUL),将grep -v 更改为grep -zv 并更改xargsxargs -0(使 grep 和 xargs 期望 NUL 作为分隔符)。
    • @pydave 如果文件名包含换行符,那将无济于事。正确的解决方案是使用-zls-tree 而不是| tr "\n" "\0",这样整个管道从头到尾都没有歧义。 (因为 NUL/ 是 POSIX 兼容文件系统上文件名中仅有的两个不允许的字符。)
    【解决方案2】:

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

    #!/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 (i.e. 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
    

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

    【讨论】:

    • 非常感谢分享!这在测试回购中对我有用。我还添加了if [ "$(cat $1)" == "" ]; then echo "No content in exclude file" exit 1 fi 以检查提供的文件是否存在。此外,似乎需要提供排除文件的完整路径。
    • 附注此外,排除文件的最后一行应该是空的/垃圾。
    • 我喜欢挑选和选择要保留的文件的想法......但按照设计,这将需要超过 20 小时才能在具有 30K 提交的 repo 上运行......
    【解决方案3】:

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

    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
    

    该解决方案基于 Jefromi 的回答和Detach (move) subdirectory into separate Git repository 以及这里的许多 cmets。

    Jefromi 的解决方案对我不起作用的原因是,我的 repo 中有文件和文件夹,其名称包含特殊字符(主要是空格)。另外git rm 抱怨文件不匹配(通过--ignore-unmatch 解决)。

    您可以使过滤与不在 repo 的根目录中或被移动的目录无关:

    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
    

    【讨论】:

      【解决方案4】:

      使用git-filter-repo 从 2.25 版开始,这不是 git 的一部分。 这需要 Python3 (>=3.5) 和 git 2.22.0

      mkdir new_repoA
      mkdir new_repoB
      git clone originalRepo newRepoA
      git clone originalRepo newRepoB
      
      pushd
      cd new_repoA
      git filter-repo --path foo/bar --path foo/baz
      
      popd
      cd new_repoB 
      git filter-repo --path foo/qux
      

      对于包含约 12000 次提交的我的 repo,git-filter-branch 耗时超过 24 小时,git-filter-repo 耗时不到一分钟。

      【讨论】:

        【解决方案5】:

        更简洁的方法:

        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 会提早进行修剪,使用这么便宜的过滤器并不重要,但无论如何避免浪费精力是件好事。

        【讨论】:

          【解决方案6】:

          如果您希望将单个目录拆分为单独的 git 存储库

          git-filter-branch--subdirectory-filter 选项,它比前面提到的解决方案简单得多,只是:

          git filter-branch --subdirectory-filter foodir -- --all
          

          此外,它会更改路径并将目录的内容放在新仓库的顶部,而不仅仅是过滤和删除其他内容。

          【讨论】:

            【解决方案7】:

            我将git-filter-repofilename-callback 一起使用。

            stephen@B450-AORUS-M:~/source/linux$ git filter-repo --force --filename-callback '
              if b"it87.c" in filename:
                return filename
              else:
                # Keep the filename and do not rename it
                return None
              '
            warning: Tag points to object of unexpected type tree, skipping.
            warning: Tag points to object of unexpected type tree, skipping.
            Parsed 935794 commitswarning: Omitting tag 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c,
            since tags of trees (or tags of tags of trees, etc.) are not supported.
            warning: Omitting tag 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c,
            since tags of trees (or tags of tags of trees, etc.) are not supported.
            Parsed 937142 commits
            New history written in 177.03 seconds; now repacking/cleaning...
            Repacking your repo and cleaning out old unneeded objects
            HEAD is now at a57e6edb85a3 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157
            Enumerating objects: 20210, done.
            Counting objects: 100% (20210/20210), done.
            Delta compression using up to 12 threads
            Compressing objects: 100% (17718/17718), done.
            Writing objects: 100% (20210/20210), done.
            Total 20210 (delta 1841), reused 20038 (delta 1669), pack-reused 0
            Completely finished after 179.76 seconds.
            

            它没有删除空的合并提交,可能是由于与树的一侧相关联的一堆标签。

            我尝试使用投票最多的答案,但它似乎没有删除任何内容,并且花了很长时间。

            Rewrite 3e80e1395bd4f410b79dc0f17113f5b6b409c7d8 (329/937142) (8 seconds passed, remaining 22779 predicted)
            

            22779 秒 = 6.3275 小时

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-08-11
              • 2014-01-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-07-24
              相关资源
              最近更新 更多