关于合并:如何将现有的Git存储库导入另一个?

How to import existing Git repository into another?

我在一个名为xxx的文件夹中有一个Git存储库,还有第二个名为yyy的Git存储库。

我想将XXX存储库作为名为zzz的子目录导入到YYY存储库中,并将所有XXX的更改历史添加到YYY中。

之前的文件夹结构:

1
2
3
4
5
6
XXX
 |- .git
 |- (project files)
YYY
 |- .git
 |- (project files)

之后的文件夹结构:

1
2
3
4
5
YYY
 |- .git  <-- This now contains the change history from XXX
 |-  ZZZ  <-- This was originally XXX
      |- (project files)
 |-  (project files)

可以这样做吗,还是必须使用子模块?


最简单的方法可能是将xxx文件拉入YYY中的一个分支,然后将其合并到master中:

在YYY:

1
2
3
4
5
6
7
8
9
10
11
12
git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff                      # repeat as necessary for each file/dir
git commit -m"Moved stuff to ZZZ"
git checkout master                
git merge ZZZ --allow-unrelated-histories   # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ                           # to get rid of the extra branch before pushing
git push                                    # if you have a remote, that is

实际上,我只是试了几次我的休息,它工作。不同于J?RG的答案是,它不会让你继续使用其他回购协议,但我认为你无论如何也没有指定。

注意:由于这是最初在2009年写的,Git添加了下面答案中提到的子树合并。我今天可能会使用那个方法,当然这个方法仍然有效。


如果您希望保留第二个存储库的确切提交历史记录,因此也可以保留将来轻松合并上游更改的能力,那么这里是您想要的方法。它会导致导入到您的repo中的子树的未修改历史记录加上一个合并提交,以将合并的存储库移动到子目录。

1
2
3
4
5
git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m"Imported XXX as a subtree."

您可以像这样跟踪上游更改:

1
git pull -s subtree XXX_remote master

在进行合并之前,Git会自己计算出根在哪里,所以您不需要在随后的合并中指定前缀。

2.9之前的Git版本:您不需要将--allow-unrelated-histories选项传递给git merge

另一个答案中使用read-tree跳过merge -s ours步骤的方法与用cp复制文件并提交结果的方法没有什么不同。

源代码来自Github的"子树合并"帮助文章。


git-subtree是一个专门为这个用例设计的脚本,它将多个存储库合并为一个存储库,同时保留历史(和/或拆分子树的历史,尽管这似乎与这个问题无关)。自1.7.11版本以来,它作为Git树的一部分进行分发。

修订版的库合并为ZZU1〔7〕

git-subtree implements the subtree merge strategy in a more user friendly manner.

For your case, inside repository YYY, you would run:

ZZU1〔8〕


在Git存储库中有一个众所周知的例子,在Git社区中,它被称为"有史以来最酷的合并"(在描述此合并的Git邮件列表的电子邮件中使用的主题行Linus Torvalds之后)。在这种情况下,现在是git-proper的一部分的gitkgit-gui实际上是一个单独的项目。Linus以一种

  • 它出现在Git存储库中,就好像它一直是作为Git的一部分开发的一样,
  • 所有的历史都完好无损
  • 它仍然可以在旧的存储库中独立开发,只需修改git pulled。

电子邮件包含了复制所需的步骤,但这并不是为了发自内心的懦夫:首先,Linus写了Git,所以他可能比你或我对它了解得更多;其次,这是近5年前的事了,此后Git有了很大的改进,所以现在可能更容易了。

特别是,我想现在有人会使用Gitk子模块,在那个特定的情况下。


实现这一点的简单方法是使用git格式补丁。

假设我们有2个Git存储库foo和bar。

FO包含:

  • 英尺·txt
  • Git

酒吧包含:

  • TXT
  • Git

最后我们要看到foo,其中包含了酒吧历史和这些文件:

  • 英尺·txt
  • Git
  • 福巴尔/巴特

所以这样做:

1
2
3
4
5
 1. create a temporary directory eg PATH_YOU_WANT/patch-bar
 2. go in bar directory
 3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
 4. go in foo directory
 5. git am PATH_YOU_WANT/patch-bar/*

如果我们想重写BAR的所有消息提交,我们可以这样做,例如在Linux上:

1
git filter-branch --msg-filter 'sed"1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD

这将在每个提交消息的开头添加"[栏]"。


此功能将远程repo克隆到本地repo dir,合并所有提交后将保存,git log将显示原始提交和正确的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function git-add-repo
{
    repo="$1"
    dir="$(echo"$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo"$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone"$repo""$tmp"
    cd"$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed"s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv"$GIT_INDEX_FILE.new""$GIT_INDEX_FILE"
    ' HEAD

    cd"$path"
    git remote add -f"$remote""file://$tmp/.git"
    git pull"$remote/master"
    git merge --allow-unrelated-histories -m"Merge repo $repo into master" --edit"$remote/master"
    git remote remove"$remote"
    rm -rf"$tmp"
}

如何使用:

1
2
cd current/package
git-add-repo https://github.com/example/example dir/to/save

如果稍作更改,甚至可以将合并的repo的文件/目录移动到不同的路径,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
repo="https://github.com/example/example"
path="$(pwd)"

tmp="$(mktemp -d)"
remote="$(echo"$tmp" | sed 's/\///g' | sed 's/\./_/g')"

git clone"$repo""$tmp"
cd"$tmp"

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo"$1" | sed 's/\./\\./')"
    to="$(echo"$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed"'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv"$GIT_INDEX_FILE.new""$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

cd"$path"
git remote add -f"$remote""file://$tmp/.git"
git pull"$remote/master"
git merge --allow-unrelated-histories -m"Merge repo $repo into master" --edit"$remote/master"
git remote remove"$remote"
rm -rf"$tmp"

通告路径通过sed替换,因此确保在合并后以正确的路径移动。--allow-unrelated-histories参数只存在于git>=2.9之后。


基于本文,使用子树对我来说是有效的,并且只转移了适用的历史。如果有人需要这些步骤,请在此处发布(确保用适用于您的值替换占位符):

在源存储库中,将子文件夹拆分为新的分支

git subtree split --prefix=-b subtree-split-result

在目标中,在拆分结果分支中进行repo合并

1
2
3
4
git remote add merge-source-repo <path-to-your-source-repository>
git fetch merge-source-repo
git merge -s ours --no-commit merge-source-repo/subtree-split-result
git read-tree --prefix=<destination-path-to-merge-into> -u merge-source-repo/subtree-split-result

验证更改并提交

1
2
git status
git commit

别忘了

删除subtree-split-result分支进行清理

git branch -D subtree-split-result

删除为从源repo获取数据而添加的远程

git remote rm merge-source-repo


加上另一个答案,因为我认为这有点简单。将repo dest拉入repo-to-import,然后执行push-set-upstream url:repo dest master。

这种方法对我来说很有效,将几个较小的回购协议导入到一个较大的回购协议中。

如何导入:repo1_to_import to repo dest

1
2
3
4
5
6
7
8
9
10
11
12
# checkout your repo1_to_import if you don't have it already
git clone url:repo1_to_import repo1_to_import
cd repo1_to_import

# now. pull all of repo_dest
git pull url:repo_dest
ls
git status # shows Your branch is ahead of 'origin/master' by xx commits.
# now push to repo_dest
git push --set-upstream url:repo_dest master

# repeat for other repositories you want to import

在执行导入之前,重命名文件和目录或将其移动到原始repo中所需的位置。例如

1
2
3
4
5
6
cd repo1_to_import
mkdir topDir
git add topDir
git mv this that and the other topDir/
git commit -m"move things into topDir in preparation for exporting into new repo"
# now do the pull and push to import

下面链接中描述的方法激发了这个答案。我喜欢它,因为它看起来更简单。但是当心!有龙!https://help.github.com/articles/importing-an-external-git-repository git push --mirror url:repo_dest将本地回购历史记录和状态推送到远程(url:repo-dest)。但它删除了遥控器的旧历史和状态。乐趣随之而来!-e


在我的例子中,我只想从另一个存储库(XXX)导入一些文件。子树对我来说太复杂了,其他的解决方案都不起作用。我就是这样做的:

1
2
ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '
' ' ')

这将为您提供一个空间分隔的列表,其中列出了影响我要导入的文件(zzz)的所有提交(您可能还需要添加--follow来捕获重命名)。然后我进入目标存储库(YYY),将另一个存储库(XXX)作为远程存储库添加,从中提取数据,最后:

1
git cherry-pick $ALL_COMMITS

这会将所有提交添加到您的分支中,这样您就拥有了所有具有其历史记录的文件,并且可以对它们执行您想要的任何操作,就好像它们一直都在这个存储库中一样。


我可以为您的问题建议另一个解决方案(Git子模块的替代方案)-Gil(Git链接)工具

它允许描述和管理复杂的Git存储库依赖关系。

它还提供了一个解决Git递归子模块依赖性问题的方法。

假设您有以下项目依赖项:Git存储库依赖关系图示例

然后您可以使用存储库关系描述定义.gitlinks文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master

# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master

# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master

每行以以下格式描述git链接:

  • 存储库的唯一名称
  • 存储库的相对路径(从.gitlinks文件的路径开始)
  • 将在git clone命令中使用的git存储库要签出的存储库分支
  • 不分析以开头的空行或空行(视为注释)。
  • 最后,您必须更新您的根示例存储库:

    1
    2
    3
    4
    5
    6
    # Clone and link all git links dependencies from .gitlinks file
    gil clone
    gil link

    # The same result with a single command
    gil update

    因此,您将克隆所有必需的项目,并以适当的方式将它们相互链接。

    如果您想用子链接存储库中的所有更改提交某个存储库中的所有更改,您可以用一个命令完成:

    1
    gil commit -a -m"Some big update"

    pull、push命令的工作方式类似:

    1
    2
    gil pull
    gil push

    gil(git links)工具支持以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    usage: gil command arguments
    Supported commands:
        help - show this help
        context - command will show the current git link context of the current directory
        clone - clone all repositories that are missed in the current context
        link - link all repositories that are missed in the current context
        update - clone and link in a single operation
        pull - pull all repositories in the current directory
        push - push all repositories in the current directory
        commit - commit all repositories in the current directory

    关于git递归子模块依赖性问题的更多信息。


    See Basic example in this article and consider such mapping on repositories:

    • A子目录<->YYY
    • B<->XXX

    在完成本章描述的所有活动后(合并后),删除分支B-master

    1
    $ git branch -d B-master

    然后,推动改变。

    它对我有用。


    我当时正在寻找-s theirs,但当然,这个策略不存在。我的历史是我在Github上分了一个项目,现在由于某种原因,我的本地master不能与upstream/master合并,尽管我没有对这个分支进行本地更改。(真的不知道那里发生了什么——我猜上游可能在幕后做了一些肮脏的推挤?)

    我最后做的是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # as per https://help.github.com/articles/syncing-a-fork/
    git fetch upstream
    git checkout master
    git merge upstream/master
    ....
    # Lots of conflicts, ended up just abandonging this approach
    git reset --hard   # Ditch failed merge
    git checkout upstream/master
    # Now in detached state
    git branch -d master # !
    git checkout -b master   # create new master from upstream/master

    因此,现在我的master又与upstream/master同步了(您可以对您同样希望同步的任何其他分支重复上述步骤)。


    我不知道一个简单的方法。你可以这样做:

  • 使用git filter branch在xxx存储库中添加zzz超级目录
  • 将新分支推送到YYY存储库
  • 将推过的分支与YYY的主干合并。
  • 如果这听起来有吸引力,我可以用细节来编辑。


    我想你可以用"git mv"和"git pull"来实现。

    我是一个公平的Git Noob-所以要小心你的主要存储库-但我只是在一个临时目录中尝试了这个,它似乎可以工作。

    首先-重命名XXX的结构,使其与您希望它在YYY中时的外观相匹配:

    1
    2
    3
    4
    cd XXX
    mkdir tmp
    git mv ZZZ tmp/ZZZ
    git mv tmp ZZZ

    现在,XXX看起来是这样的:

    1
    2
    3
    XXX
     |- ZZZ
         |- ZZZ

    现在使用"git pull"获取以下内容的更改:

    1
    2
    cd ../YYY
    git pull ../XXX

    现在,YYY看起来像这样:

    1
    2
    3
    4
    YYY
     |- ZZZ
         |- ZZZ
     |- (other folders that already were in YYY)