组合多个Git存储库


Combining multiple git repositories

假设我有一个类似

1
2
3
phd/code/
phd/figures/
phd/thesis/

出于历史原因,它们都有自己的Git存储库。但我想把它们合并成一个单一的,以简化一些事情。例如,现在我可能要做两组更改,并且必须做类似的事情

1
2
3
4
cd phd/code
git commit
cd ../figures
git commit

只是表演一下就好了

1
2
cd phd
git commit

似乎有两种方法可以使用子模块或从我的子存储库中提取,但这比我所寻找的要复杂一些。至少,我会很高兴

1
2
3
cd phd
git init
git add [[everything that's already in my other repositories]]

但这似乎不是一条直线。git里有什么能帮我的吗?


我在这里给出了一个解决方案:

  • 首先做一个完整的博士目录备份:我不想为你多年的辛勤工作负责任!;-)

    1
    $ cp -r phd phd-backup
  • phd/code的内容移到phd/code/code中,并修复历史记录,使其看起来一直存在(使用git的filter branch命令):

    1
    2
    3
    4
    5
    6
    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed"s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
  • phd/figuresphd/thesis的内容相同(只需用figuresthesis替换code

    现在您的目录结构应该如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
  • 然后在根目录中创建一个Git存储库,将所有内容拉入其中并删除旧的存储库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ cd phd
    $ git init

    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git

    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git

    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git

    最后,你现在应该得到你想要的:

    1
    2
    3
    4
    5
    6
    7
    8
    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
  • 这个过程的一个好方面是它将保留非版本化的文件和目录。

    希望这有帮助。

    不过,只要一句警告:如果您的code目录已经有一个code子目录或文件,事情可能会非常糟糕(当然,figuresthesis也是如此)。如果是这样,只需在执行整个过程之前重命名该目录或文件:

    1
    2
    3
    $ cd phd/code
    $ git mv code code-repository-migration
    $ git commit -m"preparing the code directory for migration"

    当程序完成后,添加最后一步:

    1
    2
    3
    $ cd phd
    $ git mv code/code-repository-migration code/code
    $ git commit -m"final step for code directory migration"

    当然,如果code子目录或文件没有版本化,只需使用mv而不是git mv就可以了,不要再使用git commit了。


    git-stitch-repo will process the output of git-fast-export --all --date-order on the git repositories given on the command-line, and create a stream suitable for git-fast-import that will create a new repository containing all the commits in a new commit tree that respects the history of all the source repositories.


    也许,简单地(类似于前面的答案,但使用更简单的命令)在每个单独的旧存储库中进行提交,将内容移动到适当命名的子目录中,例如:

    1
    2
    3
    4
    5
    $ cd phd/code
    $ mkdir code
    # This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
    $ git mv * code/
    $ git commit -m"preparing the code directory for migration"

    然后通过如下步骤将三个独立的回购合并为一个新的回购:

    1
    2
    3
    4
    5
    6
    $ cd ../..
    $ mkdir phd.all
    $ cd phd.all
    $ git init
    $ git pull ../phd/code
    ...

    然后你会保存你的历史记录,但会继续进行单一回购。


    您可以尝试子树合并策略。它将允许您将回购B合并到回购A中。与git-filter-branch相比,它的优势在于不需要您重写您的回购A历史(打破sha1总和)。


    Git过滤器分支解决方案工作良好,但请注意,如果Git repo来自SVN导入,它可能会失败,并显示如下消息:

    1
    Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

    在这种情况下,您需要从过滤器分支中排除初始版本,即将末尾的HEAD更改为[SHA of 2nd revision]..HEAD,请参见:

    http://www.git.code-experials.com/blog/2010/03/merging-git-stores.html


    亚里士多德·帕戈尔茨的答案中的git stitch repo只适用于具有简单线性历史的存储库。

    MiniQuark的答案适用于所有存储库,但它不处理标记和分支。

    我创建了一个程序,其工作方式与MiniQuark描述的相同,但它使用一个合并提交(带有n个父级),并重新创建所有标记和分支以指向这些合并提交。

    有关如何使用它的示例,请参见git合并repos存储库。


    @MiniQuark解决方案帮助了我很多,但不幸的是它没有考虑到源存储库中的标签(至少在我的情况下)。下面是我对@miniquark答案的改进。

  • 首先创建包含组合repo和合并repo的目录,为每个合并的目录创建目录。


    $ mkdir new_phd
    $ mkdir new_phd/code
    $ mkdir new_phd/figures
    $ mkdir new_phd/thesis

  • 对每个存储库进行拉取并获取所有标记。(仅为code子目录提供说明)


    $ cd new_phd/code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs/tags/*:refs/tags/*

  • (这是对miniquark答案第2点的改进)将new_phd/code的内容移动到new_phd/code/code并在每个标签前添加code_prefix。


    $ git filter-branch --index-filter 'git ls-files -s | sed"s-\t\"*-&code/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' --tag-name-filter 'sed"s-.*-code_&-"' HEAD

  • 这样做之后,标记的数量将是进行筛选分支之前的两倍。旧标签仍保留在repo中,并添加带有code_前缀的新标签。


    $ git tag
    mytag1
    code_mytag1

    手动删除旧标签:

    $ ls .git/refs/tags/* | grep -v"/code_" | xargs rm

    对其他子目录重复第2、3、4点

  • 现在我们有了目录结构,如@miniquark anwser点3所示。

  • 按照MiniQuark Anwser的第4点操作,但在执行拉操作之后和删除.gitdir之前,获取标签:


    $ git fetch catalog refs/tags/*:refs/tags/*

    继续…

  • 这只是另一个解决方案。希望它能帮助别人,它能帮助我:)


    实际上,git-stitch repo现在支持分支和标签,包括带注释的标签(我发现有一个bug,我报告了它,它得到了修复)。我发现有用的是标签。因为标签附在提交上,而一些解决方案(如EricLee的方法)无法处理标签。您尝试在导入的标记上创建一个分支,它将撤消任何Git合并/移动,并像合并存储库与标记来自的存储库几乎相同一样将您送回。此外,如果在多个"合并/合并"的存储库中使用相同的标记,则会出现问题。例如,如果您有repo的a ad b,两个都有标签rel_1.0。您将repo a和repo b合并到repo a b中。由于rel_1.0标记在两个不同的提交上(一个用于a,一个用于b),哪个标记在a b中可见?从导入的回购A或从导入的回购B中选择标签,但不能同时选择两者。

    git-stitch repo通过创建rel_1.0-a和rel_1.0-b标签来帮助解决这个问题。您可能无法签出rel_1.0标记并期望两者同时存在,但至少您可以看到两者,并且理论上,您可以将它们合并到一个公共的本地分支,然后在合并的分支上创建一个rel_1.0标记(假设您只是合并而不更改源代码)。最好是与分支机构合作,因为您可以像从每个回购中合并分支机构一样将其合并到本地分支机构中。(可以将dev-a和dev-b合并到本地dev分支中,然后将其推送到源站)。


    我已经创建了一个完成此任务的工具。所使用的方法类似(在内部生成一些东西,如过滤器分支),但更友好。是GPL 2

    http://github.com/geppo12/gitcombinerepo


    你建议的顺序

    1
    2
    3
    git init
    git add *
    git commit -a -m"import everything"

    会有效,但您将丢失提交历史记录。


    要在主项目中合并第二个项目,请执行以下操作:

    a)在第二个项目中

    1
    git fast-export --all --date-order > /tmp/secondProjectExport

    b)在主项目中:

    1
    2
    git checkout -b secondProject
    git fast-import --force < /tmp/secondProjectExport

    在这个分支中,完成您需要做的所有繁重的转换并提交它们。

    c)然后返回主节点,并在两个分支之间进行经典合并:

    1
    2
    git checkout master
    git merge secondProject


    我也会把我的解决方案放在这里。它基本上是围绕git filter-branch的一个相当简单的bash脚本包装器。与其他解决方案一样,它只迁移主分支,不迁移标记。但是完整的主提交历史被迁移了,它是一个简短的bash脚本,因此用户应该相对容易地审查或调整它。

    https://github.com/oakleon/git-join-repos