我在名为 XXX 的文件夹中有一个Git存储库,我有第二个名为 YYY 的Git存储库。
我想将 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)
可以这样做,还是我必须使用子模块?
答案 0 :(得分:385)
最简单的方法可能是将 XXX 内容添加到 YYY 的分支中,然后将其合并到主数据库中:
在 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不同,它不会让你继续使用其他回购,但我认为无论如何你都没有指定。
注意:由于这最初是在2009年编写的,因此git添加了下面答案中提到的子树合并。我今天可能会使用这种方法,虽然这种方法当然仍有效。
答案 1 :(得分:347)
如果您想保留第二个存储库的确切提交历史记录,因此也保留了将来轻松合并上游更改的功能,那么这就是您想要的方法。它导致子树的未修改历史记录被导入到您的仓库中,加上一个合并提交将合并的存储库移动到子目录。
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在进行合并之前根据其自身的位置计算出来,因此您无需在后续合并中指定前缀。
2.9之前的Git版本:您无需将--allow-unrelated-histories
选项传递给git merge
。
使用read-tree
并跳过merge -s ours
步骤的另一个答案中的方法实际上与使用cp复制文件并提交结果没有什么不同。
答案 2 :(得分:47)
Git存储库本身就有一个众所周知的例子,它在Git社区中统称为“the coolest merge ever”(在电子邮件中使用的主题行Linus Torvalds之后给Git描述此合并的邮件列表)。在这种情况下,gitk
Git GUI现在是Git的一部分,实际上曾经是一个单独的项目。 Linus设法以
git pull
。电子邮件包含了重现所需的步骤,但它不适合胆小的人:首先,Linus 写了 Git,所以他可能比你或我更了解它,第二,这差不多5年前,Git从那时起已经改进了相当,所以现在可能更容易了。
特别是,我想现在有人会在特定情况下使用gitk子模块。
答案 3 :(得分:12)
这样做的简单方法是使用git format-patch。
假设我们有2个git存储库 foo 和 bar 。
foo 包含:
bar 包含:
我们希望以 foo 包含 bar 历史记录和这些文件结束:
所以要做到这一点:
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]”。
答案 4 :(得分:6)
基于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
答案 5 :(得分:6)
此函数将远程repo克隆到本地repo dir,合并后将保存所有提交,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
替换,因此请确保在合并后将其移动到正确的路径中
由于git&gt; = 2.9。
--allow-unrelated-histories
参数仅存在
答案 6 :(得分:3)
添加另一个答案,因为我认为这有点简单。将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
答案 7 :(得分:3)
让我使用名称a
(代替XXX
和ZZZ
)和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
是discouraged)。
合并两个大型存储库,然后将其中一个放入子目录的示例:https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
更多here。
答案 8 :(得分:1)
在我的情况下,我只想从其他存储库(XXX)导入一些文件。子树对我来说太复杂了,其他解决方案也不起作用。这就是我所做的:
ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')
这为您提供了一个以空格分隔的列表,列出了所有以相反顺序影响我要导入的文件(ZZZ)的提交(您可能还需要添加--follow以捕获重命名)。然后我进入目标存储库(YYY),将另一个存储库(XXX)添加为远程,从中获取并最终:
git cherry-pick $ALL_COMMITS
将所有提交添加到您的分支,因此您将拥有所有具有其历史记录的文件,并且可以随意使用它们,就像它们一直存在于此存储库中一样。
答案 9 :(得分: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
同步(你可以对你想要同步的任何其他分支重复上述内容。)
答案 10 :(得分:0)
请参阅this article中的基本示例,并考虑对存储库进行此类映射:
A
&lt; - &gt; YYY
,B
&lt; - &gt; XXX
在本章描述的所有活动(合并后)之后,删除分支B-master
:
$ git branch -d B-master
然后,推动更改。
它对我有用。
答案 11 :(得分:0)
对于您的问题,我可以建议另一种解决方案(替代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
每行以以下格式描述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链接)工具支持以下命令:
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
答案 12 :(得分:0)
这是可以直接在蝙蝠身上工作的脚本。
#!/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
答案 13 :(得分:0)
没有足够的代表来为 x-yuri 的答案添加评论,但它运行良好并保留了历史。 我正在使用两个工作本地存储库并收到此错误:
<块引用>中止:拒绝破坏性地覆盖回购历史,因为 这看起来不像一个新的克隆。 (预计新鲜包装的回购) 请改为在新的克隆上操作。如果您仍想继续,请使用 --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
是您为要导入的存储库的根文件夹指定的名称。
答案 14 :(得分:-1)
我不知道一个简单的方法。你可以这样做:
如果听起来很有吸引力,我可以编辑细节。
答案 15 :(得分:-2)
我认为你可以使用'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)