繁体   English   中英

如何在保留子目录的同时拆分 git 存储库?

[英]How to split a git repository while preserving subdirectories?

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

我有这个:

foo/
  .git/
  bar/
  baz/
  qux/

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

foo/
  .git/
  bar/
  baz/

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

如何在 git 中做到这一点?

如果有某种方法可以在整个历史记录中将所有新存储库的内容移动到子目录中,我可以使用此答案中的方法。

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

这是手册页中的示例:

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 的顶层运行索引过滤器。
  • grep 可能有点矫枉过正,但我​​认为这不是速度问题。
  • --all将此应用于所有引用; 我想你真的想要那个。 --将它与过滤器分支选项分开)
  • -z-0告诉 ls-tree、grep 和 xargs 使用 NUL 终止来处理文件名中的空格。

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

--prune-empty

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

我想做一个类似的事情,但由于我想保留的文件列表很长,使用无数的 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

令人惊讶的是,结果证明这比我最初预期的要多得多,所以我决定把它贴在这里。

当我自己遇到这个问题时,这就是我最终为解决这个问题所做的:

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) 子目录到单独的 Git 存储库中,以及此处关于 SO 的许多评论。

Jefromi 的解决方案对我不起作用的原因是,我的存储库中有文件和文件夹,其名称包含特殊字符(主要是空格)。 此外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

使用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 次提交的存储库, git-filter-branch花费了 24 多个小时,而git-filter-repo花费了不到一分钟。

更清洁的方法:

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

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

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

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

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

我将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 小时

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM