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

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

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

之前的文件夹结构:

├── 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)

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

248322 次浏览

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

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

首先-重命名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)

我不知道有什么简单的办法。你可以这样做:

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

如果听起来吸引人,我可以修改细节。

在Git存储库中有一个著名的例子,在Git社区中被统称为& 最酷的合并"(在Linus Torvalds发送给Git邮件列表的描述此合并的电子邮件的主题行之后)。在这种情况下,gitk Git GUI现在是Git的一部分,实际上曾经是一个单独的项目。Linus设法将该存储库合并到Git存储库中

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

电子邮件包含了复制所需的步骤,但它不适合胆小的人:首先,Linus 写了 Git,所以他可能比你我更了解它,其次,这是近5年前的事情了,从那时起Git已经改进了大大,所以现在可能更容易了。

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

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

在# EYZ0:

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

实际上,我刚刚用我的几个回购尝试了这个,它是有效的。不像Jorg的回答,它不会让你继续使用另一个回购,但我不认为你指定无论如何。

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

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

假设我们有两个git存储库喷火酒吧

# EYZ0包含:

  • foo.txt
  • .

# EYZ0包含:

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

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

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

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

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

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

但这不会显示合并历史中其他的变化。

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

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

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

原始来源是github的“子树合并”帮助文章。和# EYZ1。

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

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

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

git cherry-pick $ALL_COMMITS

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

添加另一个答案,因为我认为这有点简单。将repo_dest拉入到repo_to_import中,然后推入——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

重命名或移动文件和dirs到原始回购所需的位置,然后再进行导入。如。

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

我在寻找-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同步(您可以对任何其他分支重复上述操作,您也想类似地同步)。

基于关于本文,使用子树是什么为我工作,只有适用的历史被转移。在这里发布,以防有人需要这些步骤(确保将占位符替换为适用于你的值):

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

# EYZ0

在你的目标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分支进行清理

# EYZ0

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

# EYZ0

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

要将修订版<rev>中的<repo>存储库合并为子目录<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

但这不会显示合并历史中其他的变化。

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

更多关于在这里

请参阅这篇文章中的基本的例子,并考虑在存储库上进行这样的映射:

  • # EYZ0 & lt; - > # EYZ1,
  • & # 8226;- # 8226

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

$ git branch -d B-master

然后,推动更改。

这对我很管用。

这个函数会将远程repo复制到本地repo目录,合并后保存所有提交,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

如果做一点改变,你甚至可以移动文件/dirs合并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"
< p > # EYZ0 < br > 路径替换通过sed,所以确保它在合并后移动到正确的路径 --allow-unrelated-histories参数只存在,因为git >= 2.9

我可以为你的问题提出另一个解决方案(替代git-submodules) - Gil (git链接)工具

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

它还提供了Git递归子模块依赖问题. #的解决方案。

考虑你有以下项目依赖项: # EYZ0 < / p >

然后你可以用存储库关系描述定义.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克隆命令 存储库分支签出
  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"

Pull、push命令的工作原理类似:

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

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

假设你想合并存储库ab(我假设它们位于彼此旁边):

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

更多关于在这里

下面是脚本,将立即工作。

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

现在将主分支上的更改推到remote/origin。根据您要做的事情,可能不需要此步骤。

git push origin master

没有足够的代表为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 a中,a是你要导入的repo根文件夹的名称