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

Posted

技术标签:

【中文标题】如何将现有的 Git 存储库导入另一个?【英文标题】:How to import existing Git repository into another? 【发布时间】:2010-12-13 14:26:56 【问题描述】:

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

我想将 XXX 存储库作为名为 ZZZ 的子目录导入到 YYY 存储库并添加所有 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)

这可以做到吗,还是我必须求助于使用子模块?

【问题讨论】:

在 Github 上,现在可以在创建新仓库时从 Web 界面执行此操作 How do you merge two git repositories?的可能重复 【参考方案1】:

这是可以立即运行的脚本。

#!/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

运行脚本。转到您希望合并其他 repo 的 repo,然后运行脚本。

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

【讨论】:

效果很好,谢谢!【参考方案2】:

没有足够的代表来为 x-yuri 的答案添加评论,但它工作得很好并且保留了历史。 我正在使用两个有效的本地仓库并收到此错误:

Aborting:拒绝破坏性地覆盖回购历史,因为 这看起来不像是一个新的克隆。 (预计新包装的回购) 请改为对新克隆进行操作。如果您仍想继续,请使用 --force。

我没有担心--force 标志的含义,而是首先在本地克隆了repo:

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

并将这个新克隆的副本用于 x-yuri 布置的一系列命令。 最后,在:git filter-repo --to-subdirectory-filter aa 是您为要导入的 repo 的根文件夹指定的名称。

【讨论】:

【参考方案3】:

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

要将修订版&lt;rev&gt; 的存储库&lt;repo&gt; 合并为子目录&lt;prefix&gt;,请使用git subtree add,如下所示:

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

git-subtree 以更加用户友好的方式实现subtree merge strategy。

对于您的情况,在存储库 YYY 中,您将运行:

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

缺点是在合并的历史中文件没有前缀(不在子目录中)。结果,git log ZZZ/a 将向您显示所有更改(如果有),但合并历史记录中的更改除外。你可以这样做:

git log --follow -- a

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

换句话说,如果您不更改存储库XXXZZZ 的文件,那么您需要指定--follow 和一个不带前缀的路径。如果您在两个存储库中更改它们,那么您有 2 个命令,其中没有一个显示所有更改。

更多信息here。

【讨论】:

如果您有要合并的目录而不是裸存储库或远程,git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name Noob 体验:git(版本 2.9.0.windows.1)响应“致命:模棱两可的参数 'HEAD':未知修订或路径不在工作树中”当我在新初始化的情况下尝试此操作时,本地的,非裸存储库,但是在我真正启动新存储库之后它工作得很好,即在添加一个普通文件并提交常规方式之后。 非常适合我的场景。 哦,这太棒了。【参考方案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 在进行合并之前会自行确定根的位置,因此您无需在后续合并中指定前缀。

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

git log --follow -- a

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

换句话说,如果您不更改存储库XXX 中的ZZZ 的文件,那么您需要指定--follow 和一个不带前缀的路径。如果您在两个存储库中更改它们,那么您有 2 个命令,其中没有一个显示所有更改。

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

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

原始来源来自github's "Subtree Merge" help article。还有another useful link。

【讨论】:

这似乎没有保留历史......如果我对我拉入的任何文件执行git log,我只会看到单个合并提交,而它在其他回购? Git 1.8.0 啊哈!如果我使用导入文件的旧路径,即省略它被导入的子目录,那么 git log 会给我提交历史,例如 git log -- myfile 而不是 git log -- rack/myfile @FrancescoFrassinelli,这不是很理想吗?引入历史是这种方法的一个特点 @FrancescoFrassinelli,如果你不想要历史,为什么不做一个普通的副本?我试图弄清楚如果不是因为历史,什么会吸引你使用这种方法——这是我使用这种方法的唯一原因! 从 Git 2.9 开始,合并时需要 --allow-unrelated-histories 选项。【参考方案5】:

让我使用名称a(代替XXXZZZ)和b(代替YYY),因为这样会使说明更易于阅读。

假设您要将存储库 a 合并到 b(我假设它们彼此并排):

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-repofilter-branch 是 discouraged)。

合并 2 个大存储库的示例,将其中一个放入子目录:https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

更多信息here。

【讨论】:

优秀。与git subtree add -P ... 的解决方案不同,历史记录出现在git log 中没有问题。 原始请求者想要的一件事是 XXX 位于 ZZZ 文件夹中。因此使用了“git mv stuff ZZZ/stuff”命令。我看不出您的解决方案如何满足该要求。【参考方案6】:

我可以针对您的问题提出另一种解决方案(git-submodules 的替代方案) - gil (git links) tool

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

它还为git recursive submodules dependency problem 提供了解决方案。

假设您有以下项目依赖项: sample git repository dependency graph

然后你可以定义.gitlinks文件和repositories关系描述:

# 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 存储库 用于结帐的存储库分支 不解析空行或以 # 开头的行(视为注释)。

最后,您必须更新您的根示例存储库:

# 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 links)工具支持以下命令:

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 recursive submodules dependency problem。

【讨论】:

【参考方案7】:

可能最简单的方法是将 XXX 东西拉到 YYY 的一个分支中,然后将其合并到 master 中:

YYY

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 不同,它不会让您继续使用其他 repo,但我认为您无论如何都没有指定。

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

【讨论】:

谢谢。我使用了您的技术的略微修改版本:我在 XXX 上创建了一个“暂存”分支,在其中创建了 ZZZ 文件夹,并将“东西”移入其中。然后我把 XXX 并入 YYY。 这对我很有用。我所做的唯一更改是:1)在推送之前“git branch -d ZZZ”,因为我不希望这个临时分支挂在周围。 2)“git push”给了我错误:“没有共同的参考,也没有指定;什么都不做。也许你应该指定一个分支,比如'master'。” (我推送的源是一个空的裸存储库。)但是“git push --all”就像一个冠军。 我想在 YYY 存储库中只保留 ZZZ 文件夹和历史记录:我想删除原始 XXX 存储库和 YYY 存储库中的 ZZZ 分支。我发现删除了 ZZZ 分支,因为 @CrazyPyro 建议删除历史记录——为了保留它,我在删除之前将 ZZZ 分支合并到 master 中。 @SebastianBlask 我刚刚在我的两个存储库中搞砸了这个,并意识到有一个似乎没有人注意到的缺失步骤,尽管我多年来对此表示赞同。 :-) 我提到将它合并到 master 中,但实际上并没有显示它。现在正在编辑... 您可以在将文件移动到子文件夹时添加这样的内容:git mv $(ls|grep -v &lt;your foldername&gt;) &lt;your foldername&gt;/ 这会将所有文件和文件夹复制到您的新文件夹中【参考方案8】:

此功能会将远程仓库克隆到本地仓库目录,合并后所有提交将被保存,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 的文件/目录移动到不同的路径中,例如:

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 后才存在。

【讨论】:

对于 OS X 用户,请安装 gnu-sed 以使 git-add-repo 功能正常工作。再次感谢安德烈!【参考方案9】:

在 Git 存储库本身中有一个众所周知的实例,在 Git 社区中统称为“the coolest merge ever”(在发送给 Git 邮件列表的电子邮件中使用的主题行 Linus Torvalds 之后描述了这个合并)。在这种情况下,gitk Git GUI 现在是 Git 本身的一部分,实际上曾经是一个单独的项目。 Linus 设法将该存储库合并到 Git 存储库中

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

这封电子邮件包含了重现所需的步骤,但它不适合胆小的人:首先,Linus 编写 Git,所以他可能比你或我更了解它,其次,这几乎是 5 年前的事了,从那时起 Git 有了相当大的改进,所以现在可能要容易得多。

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

【讨论】:

顺便说一句。用于后续合并(如果有的话)的策略称为 subtree 合并,并且有第三方 git-subtree 工具可以帮助您:github.com/apenwarr/git-subtree 谢谢,我忘了。 subtree 合并策略,尤其是与 git-subtree 工具结合使用是一个不错的,甚至可能是子模块的更好替代方案。【参考方案10】:

请参阅this article 中的基本示例,并考虑在存储库上进行此类映射:

A YYY, B XXX

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

$ git branch -d B-master

然后,推送更改。

它对我有用。

【讨论】:

【参考方案11】:

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

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

git subtree split --prefix=&lt;source-path-to-merge&gt; -b subtree-split-result

在你的目标 repo 中合并拆分结果分支

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

删除您添加的远程以从源 repo 获取数据

git remote rm merge-source-repo

【讨论】:

【参考方案12】:

我当时正在寻找-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

所以现在我的master 再次与upstream/master 同步(您可以对您也想类似同​​步的任何其他分支重复上述操作)。

【讨论】:

本地master 分支上的git reset --hard upstream/master 可以完成这项工作。这样你就不会丢失本地分支配置——比如默认的上游。【参考方案13】:

添加另一个答案,因为我认为这更简单。将 repo_dest 拉入到 repo_to_import 中,然后进行 push --set-upstream url:repo_dest master。

这种方法对我将几个较小的存储库导入到一个较大的存储库中很有效。

如何导入:repo1_to_import 到 repo_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

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

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

【讨论】:

【参考方案14】:

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

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

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

git cherry-pick $ALL_COMMITS

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

【讨论】:

【参考方案15】:

执行此操作的简单方法是使用 git format-patch。

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

foo 包含:

foo.txt .git

条形包含:

bar.txt .git

我们希望以包含 bar 历史记录和这些文件的 foo 结尾:

foo.txt .git 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/*

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

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

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

【讨论】:

如果原始存储库包含分支和合并,git am 可能会失败。 小问题:git am 从提交消息中删除[ ] 中的任何内容。所以你应该使用不同于[bar]的标记 对我不起作用。 Got "error: foobar/mySubDir/test_host1: does not exist in index. 失败的补丁副本位于:/home/myuser/src/proj/.git/rebase-apply/patch 当你解决了这个问题,运行“git am --continue”。这是在应用 11 个补丁(共 60 个)之后。 This blog 对一个稍微不同的问题(仅移动选定的文件)有类似的答案。 我看到一个缺点,所有提交都添加到目标存储库的 HEAD。【参考方案16】:

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

    使用 git filter-branch 在 XXX 存储库上添加 ZZZ 超级目录 将新分支推送到 YYY 存储库 将推送的分支与YYY的主干合并。

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

【讨论】:

【参考方案17】:

我认为你可以使用 'git mv' 和 'git pull' 来做到这一点。

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

首先 - 重命名 XXX 的结构以匹配它在 YYY 中时的外观:

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

现在 XXX 看起来像这样:

XXX
 |- ZZZ
     |- ZZZ

现在使用 'git pull' 来获取更改:

cd ../YYY
git pull ../XXX

现在 YYY 看起来像这样:

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

【讨论】:

以上是关于如何将现有的 Git 存储库导入另一个?的主要内容,如果未能解决你的问题,请参考以下文章

如何`git submodule add`现有的子存储库?

如何将现有的项目添加到远程的git库里面!

将现有的 SVN 存储库导入 SVN 服务器 [关闭]

将现有的 GitLab 项目移动到新的子组中

如何将现有的 Android Studio 项目连接到现有的 Github 存储库

如何将现有的非文档核心数据存储转换为 uimanageddocument?