28

我目前有一个很大的 git 存储库,其中包含许多项目,每个项目都在自己的子目录中。我需要将其拆分为单独的存储库,每个项目都在自己的存储库中。

我试过git filter-branch --prune-empty --subdirectory-filter PROJECT master

然而,许多项目目录在他们的生活中经历了几次重命名,并且git filter-branch没有跟随重命名,所以实际上提取的 repo 在最后一次重命名之前没有任何历史记录。

如何有效地从一个大的 git 存储库中提取子目录,并按照该目录的所有重命名回到过去?

4

3 回答 3

18

感谢@Chronial,我能够根据我的需要编写一个脚本来按摩我的 git 存储库:

git filter-branch --prune-empty --index-filter '
    # Delete files which are NOT needed
    git ls-files -z | egrep -zv  "^(NAME1|NAME2|NAME3)" | 
        xargs -0 -r git rm --cached -q             
    # Move files to root directory
    git ls-files -s | sed -e "s-\t\(NAME1\|NAME2\|NAME3\)/-\t-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
        git update-index --index-info &&
        ( test ! -f "$GIT_INDEX_FILE.new" \
            || mv -f "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" )
'

基本上这是这样做的:

  1. 删除我需要的三个目录 NAME1、NAME2 或 NAME3之外的所有文件(一个项目在其生命周期内被重命名为 NAME1 -> NAME2 -> NAME3)。

  2. 将这三个目录中的所有内容移动到存储库的根目录。

  3. 我需要测试“$GIT_INDEX_FILE.new”是否存在,因为将 svn 导入 git 会创建没有任何文件的提交(仅目录提交)。仅当最初使用“git svn clone”创建存储库时才需要。

于 2013-02-08T01:37:52.970 回答
7

我有一个非常大的存储库,我需要从中提取一个文件夹;甚至--index-filter预计需要8个小时才能完成。这是我所做的:

  1. 获取文件夹所有过去名称的列表。在我的情况下,只有两个,old-name并且new-name.
  2. 对于每个名称:

    $ git checkout master
    $ git checkout -b filter-old-name
    $ git filter-branch --subdirectory-filter old-name
    

    这将为您提供几个断开连接的分支,每个分支都包含其中一个名称的历史记录。

  3. filter-old-name分支应该以重命名文件夹的提交结束filter-new-name分支应该以相同的提交开始。(如果有多个重命名,这同样适用:您最终会得到相同数量的分支,每个分支都有一个与下一个共享的提交。)一个应该删除所有内容,另一个应该重新创建它。确保这两个提交具有相同的内容;如果他们不这样做,则该文件除了被重命名外还被修改了,您将需要合并更改。(就我而言,我没有这个问题,所以我不知道如何解决它。)

    一个简单的检查方法是尝试在两个提交filter-new-name之上重新定位filter-old-name,然后将两个提交压缩在一起:git 应该抱怨这会产生一个空提交。(请注意,您需要在备用分支上执行此操作,然后将其删除:变基会从提交中删除 Committer 信息,从而丢失一些您想要保留的历史记录。)

  4. 下一步是将两个分支嫁接在一起,跳过重命名文件夹的两个提交。(否则会出现一个奇怪的跳转,所有内容都被删除并重新创建。)这涉及找到两个提交的完整 SHA(全部 40 个字符!)并将它们放入 git 的信息中,名称分支的提交首先,的命名分支的第二次提交。

    $ echo $NEW_NAME_SECOND_COMMIT_SHA1 $OLD_NAME_PENULTIMATE_COMMIT_SHA1 >> .git/info/grafts
    

    如果你做对了,git log --graph现在应该显示一条从新历史结束到旧历史开始的线。

  5. 这种移植目前是暂时的:它还不是历史的一部分,不会跟随克隆或推动。使其永久化:

    $ git filter-branch
    

    这将重新过滤分支而不尝试进行任何进一步的更改,从而使嫁接永久化(更改filter-new-name分支中的所有提交)。您现在应该能够删除该.git/info/grafts文件。

在所有这一切结束时,您现在应该在filter-new-name分支上拥有来自文件夹名称的所有历史记录。然后,您可以使用这个单独的存储库,或者将其合并到另一个存储库中,或者您想对这个历史记录做任何事情。

于 2017-05-15T17:17:41.790 回答
6

我认为 git 没有内置功能。您将必须构建自己的过滤器。只需使用git filter-branch --prune-empty --tree-filter YOURSCRIPT. 然后,您的脚本将必须识别正确的文件夹(可能是其中特定文件的名称,或者您可能拥有该项目过去所有名称的列表),删除其他所有内容并将文件夹内容向上移动.

如果你的 repo 真的很大,而且你没有时间运行这个脚本,你可以更快地达到同样的效果--index-filter,但是编写那个脚本会更复杂。您将不得不使用 git 命令来修改索引而不是文件系统修改命令。

于 2013-02-07T22:55:36.657 回答