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


476

我在名为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)

可以做到这一点,还是必须使用子模块?


2
在Github上,当您创建新的存储库时,现在可以从Web界面执行此操作
bgcode

Answers:


429

可能最简单的方法是将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的答案不同,它不会让您继续使用其他回购协议,但我认为您无论如何都没有指定。

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


1
谢谢。我对您的技术使用了稍微修改的版本:我在XXX上创建了一个“ staging”分支,并在其中创建了ZZZ文件夹,并将“ stuff”移动到了其中。然后我将XXX合并为YYY。
维杰·帕特尔

1
这对我来说很棒。我所做的唯一更改是:1)在推送之前使用“ git branch -d ZZZ”,因为我不希望这个临时分支挂在身边。2)“ git push”给我一个错误:“没有共同的引用,没有指定引用;什么也不做。也许您应该指定一个分支,例如'master'。” (我要推送到的源是一个空的裸仓库。)但是“ git push --all”的工作就像是一个冠军。
CrazyPyro 2011年

1
我只想在YYY存储库中仅包含ZZZ文件夹以及历史记录:我想删除原始的XXX存储库,以及YYY存储库中的ZZZ分支。我发现由于@CrazyPyro建议删除了历史记录而删除了ZZZ分支-为了保留它,我在删除之前将ZZZ分支合并到master中。
奥利·斯塔德霍姆

4
@SebastianBlask我只是用我的两个仓库弄乱了它,意识到尽管我多年来对此表示支持,但似乎没有人注意到这一遗漏的步骤。:-)我提到了将其合并到母版中,但实际上并未显示出来。现在进行编辑...
ebneter

2
在将文件移动到子文件夹时,您可以添加以下内容: git mv $(ls|grep -v <your foldername>) <your foldername>/ 这会将所有文件和文件夹复制到新文件夹中
serup 2016年

366

如果您想保留第二个存储库的确切提交历史记录,因此还保留了将来轻松合并上游更改的功能,那么这里就是您想要的方法。这将导致子树的未修改历史记录被导入到您的仓库中,再加上一次合并提交,以将合并的存储库移至子目录。

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

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

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

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

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

原始来源来自github的“ Subtree Merge”帮助文章。还有另一个有用的链接


9
这似乎没有保留历史记录...如果我git log对拉入的任何文件执行a 操作,我只看到单个合并提交,而在另一个存储库中没有以前的提交?Git 1.8.0
Anentropic

8
啊哈!如果我使用导入文件的旧路径,即省略导入文件的git log -- myfilegit log -- rack/myfile
子目录

2
@FrancescoFrassinelli,不是很理想吗?引入历史记录是此方法的功能
patrickvacek 2013年

4
@FrancescoFrassinelli,如果您不想要历史记录,为什么不只是定期复制呢?我试图弄清楚如果不是出于历史原因会吸引您使用此方法,这是我使用此方法的唯一原因!
patrickvacek

7
从Git 2.9开始,--allow-unrelated-histories进行合并时需要使用该选项。
stuXnet

112

git-subtree是一个脚本,专门用于在保留历史记录(和/或拆分子树的历史记录,但似乎与该问题无关)的同时将多个存储库合并为一个用例。自1.7.11版本以来,它作为git树的一部分分发。

要将<repo>修订版的存储库合并<rev>为子目录<prefix>,请使用git subtree add以下命令:

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

git-subtree 以更加用户友好的方式实现了子树合并策略

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

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

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

git log --follow -- a

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

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

这里更多。


4
如果您有一个目录要合并而不是一个裸仓库或远程目录,git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name
Tatsh

2
新手经验:git(版本2.9.0.windows.1)在新初始化的本地无裸存储库中尝试此操作时,回答“致命:模糊参数'HEAD':未知修订或路径不在工作树中”,但是在我真正使用了新的存储库之后,即在添加了一个普通文件并以常规方式提交之后,它运行良好。
斯坦因

在我的场景中表现出色。
约翰尼·犹他州

哦,这太棒了。
dwjohnston

我用@Tatsh建议和它的工作对我来说
胭脂红Tambascia

49

在Git仓库本身中有一个众所周知的实例,在Git社区中统称为“ 有史以来最酷的合并 ”(在发给Git邮件列表的电子邮件中使用的主题行Linus Torvalds之后,它描述了这一点)。合并)。在这种情况下,gitkGit GUI现在是Git固有的一部分,实际上曾经是一个单独的项目。Linus设法通过以下方式将该存储库合并到Git存储库中:

  • 它在Git存储库中的显示就好像它一直是作为Git的一部分开发的一样,
  • 所有的历史都保持不变
  • 仍然可以在其旧存储库中独立开发,只需git pull编辑更改即可。

该电子邮件包含重现所需的步骤,但并非出于胆怯:首先,利纳斯 Linus)撰写了吉特 Git),所以他大概比您或我了解得更多,其次,这是近5年前和Git有所改善显着从那以后,也许现在是很容易。

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


3
顺便说一句。用于后续合并(如果有)的策略称为子树合并,并且有第三方git-subtree工具可以帮助您解决此问题:github.com/apenwarr/git-subtree
JakubNarębski09年

谢谢,我忘记了这一点。该subtree合并的策略,尤其是在与结合git-subtree工具是一个很好的,甚至是更好的选择子模块。
约尔格W¯¯米塔格

12

最简单的方法是使用git format-patch。

假设我们有2个git仓库foobar

foo包含:

  • foo.txt
  • .git

包含:

  • bar.txt
  • .git

并且我们想以包含酒吧历史记录和以下文件的foo结尾:

  • foo.txt
  • .git
  • foob​​ar / 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则可能会失败。
亚当·蒙森

1
次要问题:git am [ ]从提交消息中剥离任何内容。因此,您应该使用与[bar]
HRJ 2013年

没有为我工作。得到了“错误:foobar / mySubDir / test_host1:在索引中不存在。失败的修补程序的副本位于:/home/myuser/src/proj/.git/rebase-apply/patch解决了此问题后,运行“ git am --continue”。这是在应用了11个补丁(共60个补丁)之后
oligofren 2014年

1
该博客对一个稍有不同的问题(仅移动选定的文件)有类似的答案。
Jesse Glick 2014年

我看到一个缺点,所有提交都添加到目标存储库的HEAD中。
CSchulz 2014年

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

注意
路径替换了via sed,因此请确保合并后它沿正确的路径移动。
--allow-unrelated-histories参数仅从git> = 2.9开始存在。


2
对于在那里的OS X人员,请gnu-sed进行安装以使该git-add-repo功能正常工作。再次感谢安德烈!
ptaylor

7

根据本文,使用子树对我有用,并且仅转移了适用的历史记录。如果有人需要这些步骤,请在此处发布(确保将占位符替换为适用于您的值):

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

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


3

添加另一个答案,因为我认为这有点简单。将repo_dest提取到repo_to_import中,然后完成push --set-upstream url:repo_dest主数据库。

这种方法对我来说很有效,可以将几个较小的存储库导入一个较大的存储库。

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


1

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

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

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

git cherry-pick $ALL_COMMITS

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


1

请参阅本文的基本示例,并考虑对存储库的此类映射:

  • A<-> YYY
  • B <-> XXX

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

$ git branch -d B-master

然后,推送更改。

这个对我有用。


0

我当时所处的位置一直在寻找,-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(您可以对同样想要同步的其他任何分支重复以上操作)。


1
git reset --hard upstream/master您当地master分支机构的A 可以胜任。这样,您就不会丢失本地分支配置-像默认上游这样的事情。
tomekwi

0

我可以为您的问题建议另一种解决方案(替代git-submodules-gil(git链接)工具

它允许描述和管理复杂的git仓库依赖性。

它还为git递归子模块依赖问题提供了解决方案。

考虑您具有以下项目依赖项: 样本git信息库依赖关系图

然后,您可以.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

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

  1. 存储库的唯一名称
  2. 存储库的相对路径(从.gitlinks文件的路径开始)
  3. Git存储库,将在git clone命令Repository分支中使用以进行结帐
  4. 空行或以#开头的行不会被解析(视为注释)。

最后,您必须更新您的根样本存储库:

# 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递归子模块依赖问题的更多信息。


0

让我使用名称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-repo安装(filter-branch劝阻)。

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

这里更多。


-1

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

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

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


-2

我认为您可以使用“ git mv”和“ git pull”来做到这一点。

我是一个不错的git noob-所以要注意您的主要存储库-但我只是在temp dir中尝试过,它似乎可以工作。

首先-重命名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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.