代码之家  ›  专栏  ›  技术社区  ›  Vijay Patel

如何将现有Git存储库导入另一个?

  •  418
  • Vijay Patel  · 技术社区  · 15 年前

    我在一个名为 XXX ,我有第二个Git存储库,名为 YYY

    我想进口这批货 XXX 将存储库放入 YYY 存储库作为名为 ZZZ XXX 将历史更改为 YYY .

    ├── XXX
    │   ├── .git
    │   └── (project files)
    └── YYY
        ├── .git
        └── (project files)
    

    文件夹结构之后:

    YYY
    ├── .git  <-- This now contains the change history from XXX
    ├──  ZZZ  <-- This was originally XXX
    │    └── (project files)
    └──  (project files)
    

    这可以做到吗,或者我必须使用子模块吗?

    14 回复  |  直到 5 年前
        1
  •  461
  •   Ari Seyhun    6 年前

    也许最简单的方法就是拉住 XXX YYY 然后将其合并到master中:

    在里面 :

    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's answer 它不会让你继续使用另一个回购协议,但我认为你无论如何都没有指定。

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

        2
  •  388
  •   x-yuri    4 年前

    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."
    

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

    git pull -s subtree XXX_remote master
    

    这个 缺点 在合并的历史记录中,文件是不固定的(不在子目录中)。因此 git log ZZZ/a 将显示除合并历史记录中的更改以外的所有更改(如果有)。你可以做:

    git log --follow -- a
    

    换句话说,如果你不改变 ZZZ 存储库中的文件 XXX --follow 一条不固定的道路。如果在两个存储库中都更改了它们,则有两个命令,其中没有一个显示所有更改。

    例如你不需要通过考试 --allow-unrelated-histories 选择 git merge .

    read-tree 跳过 merge -s ours 步骤实际上与使用cp复制文件并提交结果没有什么不同。

    github's "Subtree Merge" help article . 及 another useful link .

        3
  •  147
  •   x-yuri    4 年前

    git-subtree 是一个脚本,它正是为这样一个用例而设计的:将多个存储库合并为一个存储库,同时保留历史(和/或拆分子树的历史,尽管这似乎与这个问题无关)。它作为git树的一部分分发 since release 1.7.11

    合并存储库 <repo> <rev> as子目录 <prefix> ,使用 git subtree add 详情如下:

    git subtree add -P <prefix> <repo> <rev>
    

    git子树实现了 subtree merge strategy

    对于您的案例,在存储库YYY中,您将运行:

    git subtree add -P ZZZ /path/to/XXX.git master
    

    缺点 git log ZZZ/a 将显示除合并历史记录中的更改以外的所有更改(如果有)。你可以做:

    git log --follow -- a
    

    但这不会显示合并历史中的其他更改。

    换句话说,如果你不改变 ZZZ XXX ,则需要指定 --follow

    更多信息 here .

        4
  •  52
  •   Community datashaman    4 年前

    Git存储库中有一个众所周知的实例,在Git社区中统称为“ the coolest merge ever gitk GitGUI现在是Git本身的一部分,实际上曾经是一个单独的项目。Linus以一种

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

    这封电子邮件包含了复制所需的步骤,但它不是为胆小的人准备的:首先,莱纳斯 Git,所以他对它的了解可能比你或我多一点,其次,这几乎是5年前的事了,Git已经改进了 相当地

    特别是,我想现在人们会使用gitk子模块,在这种特定情况下。

        5
  •  12
  •   Damien R.    14 年前

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

    假设我们有2个git存储库 .

    包含:

    • foo.txt

    包含:

    • bar.txt
    • 吉特先生

    酒吧 历史记录和这些文件:

    • foo.txt
    • 吉特先生
    • foobar/bar.txt

     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/*
    

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

    这将在每个提交消息的开头添加“[bar]”。

        6
  •  10
  •   Andrey Izman    7 年前

    git log 将显示原始提交和正确路径:

    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"
    }
    

    如何使用:

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

    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

        7
  •  7
  •   Alex    9 年前

    基于 on this article ,对我来说,使用子树是有效的,只传输了适用的历史记录。如果有人需要这些步骤,请在此处发布(确保用适用于您的值替换占位符):

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

    git subtree split --prefix=<source-path-to-merge> -b subtree-split-result

    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
    

    验证您的更改并提交

    git status
    git commit
    

    别忘了

    subtree-split-result 分支

    git branch -D subtree-split-result

    git remote rm merge-source-repo

        8
  •  7
  •   x-yuri    4 年前

    让我用名字 a (代替 XXX ZZZ b (代替 YYY ),因为这使描述更容易阅读。

    假设要合并存储库 (我假设它们彼此相邻):

    cd a
    git filter-repo --to-subdirectory-filter a
    cd ..
    cd b
    git remote add a ../a
    git fetch a
    git merge --allow-unrelated-histories a/master
    git remote remove a
    

    git-filter-repo 安装( filter-branch discouraged ).

    https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

    更多信息 here .

        9
  •  3
  •   gaoithe    10 年前

    添加另一个答案,因为我认为这有点简单。将repo_dest拉入repo_to_import,然后推式设置上游url:repo_dest master。

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

    如何导入:repo1\u到repo\u导入到repo\u dest

    # 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中所需的位置。例如

    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

        10
  •  1
  •   Sebastian Blask    11 年前

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

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

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

    git cherry-pick $ALL_COMMITS
    

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

        11
  •  1
  •   VeLKerr    8 年前

    看见 在里面 this article

    • A <-&燃气轮机; YYY ,
    • B XXX

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

    $ git branch -d B-master
    

    然后,推动改变。

        12
  •  0
  •   tripleee    10 年前

    我当时的处境是我在寻找 -s theirs 但当然,这一战略并不存在。我的历史是我在GitHub上做了一个项目,现在出于某种原因,我的本地 master 无法与合并 upstream/master 虽然我没有对这个分支进行任何本地更改(真的不知道那里发生了什么——我想上游可能在幕后做了一些肮脏的勾当吧?)

    # 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
    

    所以现在我的 主人 上游/主 (您也可以对任何其他同样要同步的分支重复上述操作)。

        13
  •  0
  •   chronoxor    6 年前

    我可以建议另一种解决方案(替代 git-submodules )为了你的问题- gil (git links) tool

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

    此外,它还提供了一个解决方案 git recursive submodules dependency problem .

    考虑以下项目依赖: sample git repository dependency graph

    然后你可以定义 .gitlinks 具有存储库关系描述的文件:

    # 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
    

    1. 存储库的唯一名称
    2. 将在Git clone命令中使用的Git存储库
    3. 不解析空行或以#开头的行(视为注释)。

    # Clone and link all git links dependencies from .gitlinks file
    gil clone
    gil link
    
    # The same result with a single command
    gil update
    

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

    如果要提交某个存储库中的所有更改以及子链接存储库中的所有更改,可以使用单个命令:

    gil commit -a -m "Some big update"
    

    gil pull
    gil push
    

    Gil(git链接)工具支持以下命令:

    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递归子模块依赖问题 .

        14
  •  0
  •   A. K.    3 年前

    下面是一个脚本,它将在蝙蝠的右边工作。

    #!/bin/bash -xe
    # script name: merge-repo.sh
    # To merge repositories into the current.
    # To see the log of the new repo use 'git log --follow -- unprefixed-filename'
    # So if the file is repo/test.cpp use 'git log --follow -- test.cpp'
    # I'm not sure how this will work when two files have the same name.
    #
    # `git branch -a` will show newly created branches.
    # You can delete them if you want.
    merge_another() {
        repo="$1" # url of the remote repo
        rn="$2"   # new name of the repo, you can keep the same name as well.
        git remote add ${rn} ${repo}
        git fetch ${rn}
        git merge -s ours --no-commit --allow-unrelated-histories ${rn}/master
        git read-tree --prefix=${rn}/ -u ${rn}/master
        git commit -m "Imported ${rn} as a subtree."
        git pull -s subtree ${rn} master
    }
    
    merge_another $1 $2
    

    运行脚本。转到要合并其他回购协议的回购协议,然后运行脚本。

    cd base-repo
    ./merge-repo.sh git@github.com:username/repo-to-be-merged.git repo-to-be-merged-new-name
    

    git push origin master
    
        15
  •  0
  •   Joe Westra    3 年前

    没有足够的代表给x-yuri的答案添加评论,但它运行得很好,保留了历史。 我正在与两个正在工作的本地回购进行合作,并收到以下错误:

    中止:拒绝以破坏性方式覆盖回购历史记录 (预计新包装回购)

    而不是担心 --force

    cd tempDir
    git clone <location of repo to be merged> --no-local
    

    并将这个新克隆的副本用于x-yuri发布的一系列命令。 最后,在: git filter-repo --to-subdirectory-filter a , a

        16
  •  -1
  •   Walter Mundt    15 年前

    我不知道有什么简单的方法可以做到这一点。您可以这样做:

    如果听起来很吸引人,我可以编辑细节。

        17
  •  -2
  •   Aaron    15 年前

    我想你可以用“git-mv”和“git-pull”来实现这一点。

    我是一个普通的GitNoob——所以要小心你的主存储库——但我只是在一个临时目录中尝试了一下,它似乎起了作用。

    cd XXX
    mkdir tmp
    git mv ZZZ tmp/ZZZ
    git mv tmp ZZZ
    

    XXX
     |- ZZZ
         |- ZZZ
    

    cd ../YYY
    git pull ../XXX
    

    YYY
     |- ZZZ
         |- ZZZ
     |- (other folders that already were in YYY)