如何合并两个 Git 存储库?
考虑以下场景:
我在自己的 Git 存储库中开发了一个小型实验项目 A。它现在已经成熟了,我希望 A 成为更大的项目 B 的一部分,该项目有自己的大存储库。我现在想将 A 添加为 B 的子目录。
如何将 A 合并到 B 中,而不丢失任何一侧的历史记录?
Consider the following scenario:
I have developed a small experimental project A in its own Git repo. It has now matured, and I'd like A to be part of larger project B, which has its own big repository. I'd now like to add A as a subdirectory of B.
How do I merge A into B, without losing history on any side?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(27)
如果要将分支
some-branch
从project-a
合并到project-b
:取自:git 合并不同的存储库?
这种方法对我来说效果很好,它更短,在我看来更干净。
如果您想将
project-a
放入子目录中,可以使用git-filter-repo
(filter-branch
是 不鼓励)。在上述命令之前运行以下命令:合并 2 个大存储库的示例,将其中一个存储库放入子目录中: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
注意:
--allow-unlated-histories
参数仅自 git >= 2.9 起存在。请参阅 Git - git merge 文档 / --allow-无关历史更新:
按照@jstadler的建议添加了
--tags
以保留标签。If you want to merge branch
some-branch
fromproject-a
intoproject-b
:Taken from: git merge different repositories?
This method worked pretty well for me, it's shorter and in my opinion a lot cleaner.
In case you want to put
project-a
into a subdirectory, you can usegit-filter-repo
(filter-branch
is discouraged). Run the following commands before the commands above:An example of merging 2 big repositories, putting one of them into a subdirectory: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
Note:
The
--allow-unrelated-histories
parameter only exists since git >= 2.9. See Git - git merge Documentation / --allow-unrelated-historiesUpdate:
Added
--tags
as suggested by @jstadler in order to keep tags.这里有两种可能的解决方案:
子模块
将存储库 A 复制到较大项目 B 中的单独目录中,或者(也许更好)将存储库 A 克隆到项目 B 中的子目录中。然后使用 git 子模块 以使该存储库是存储库 B 的子模块。
对于松散耦合的存储库来说,这是一个很好的解决方案,其中存储库 A 中的开发继续进行,并且开发的主要部分是单独的A 中的独立开发。另请参阅 SubmoduleSupport 和 GitSubmoduleTutorial Git Wiki 上的页面。
子树合并
您可以使用子树合并策略将存储库 A 合并到项目 B 的子目录中。 子树合并与您对此进行了描述马库斯·普林茨。
(Git >= 2.9.0 需要选项
--allow-unlated-histories
。)或者您可以使用 git subtree 工具 (GitHub 上的存储库)由 apenwarr (Avery Pennarun) 发布,例如在他的博客文章中Git 子模块的新替代方案:git subtree。
我认为在你的情况下(A是更大的项目B的一部分),正确的解决方案是使用子树合并。
Here are two possible solutions:
Submodules
Either copy repository A into a separate directory in larger project B, or (perhaps better) clone repository A into a subdirectory in project B. Then use git submodule to make this repository a submodule of a repository B.
This is a good solution for loosely-coupled repositories, where development in repository A continues, and the major portion of development is a separate stand-alone development in A. See also SubmoduleSupport and GitSubmoduleTutorial pages on Git Wiki.
Subtree merge
You can merge repository A into a subdirectory of a project B using the subtree merge strategy. This is described in Subtree Merging and You by Markus Prinz.
(Option
--allow-unrelated-histories
is needed for Git >= 2.9.0.)Or you can use git subtree tool (repository on GitHub) by apenwarr (Avery Pennarun), announced for example in his blog post A new alternative to Git submodules: git subtree.
I think in your case (A is to be part of larger project B) the correct solution would be to use subtree merge.
另一个存储库的单个分支可以轻松地放置在保留其历史记录的子目录下。例如:
这将显示为单个提交,其中 Rails master 分支的所有文件都添加到“rails”目录中。
然而,提交的标题包含对旧历史树的引用:
其中
是 SHA-1 提交哈希。你仍然可以看到历史,归咎于一些变化。请注意,您无法从这里看到目录前缀,因为这是一个完整的实际旧分支。
您应该将其视为通常的文件移动提交:到达它时您将需要额外的跳转。
还有更复杂的解决方案,例如手动执行此操作或重写历史记录,如其他答案中所述。
git-subtree 命令是官方 git-contrib 的一部分,一些数据包管理器默认安装它(OS X Homebrew)。
但除了 git 之外,你可能还需要自己安装它。
A single branch of another repository can be easily placed under a subdirectory retaining its history. For example:
This will appear as a single commit where all files of Rails master branch are added into "rails" directory.
However the commit's title contains a reference to the old history tree:
Where
<rev>
is a SHA-1 commit hash. You can still see the history, blame some changes.Note that you can't see the directory prefix from here since this is an actual old branch left intact.
You should treat this like a usual file move commit: you will need an extra jump when reaching it.
There are more complex solutions like doing this manually or rewriting the history as described in other answers.
The git-subtree command is a part of official git-contrib, some packet managers install it by default (OS X Homebrew).
But you might have to install it by yourself in addition to git.
如果您想单独维护项目,子模块方法是很好的选择。但是,如果您确实想将两个项目合并到同一个存储库中,那么您还有更多工作要做。
第一件事是使用 git filter-branch 重写第二个存储库中所有内容的名称,使其位于您希望它们结束的子目录中。因此,您将拥有
projb/foo.c
和projb/bar.html<,而不是
foo.c
、bar.html
/代码>。然后,您应该能够执行如下操作:
git pull
将执行git fetch
,然后执行git merge
。如果您要拉取的存储库还没有projb/
目录,则应该不会发生冲突。进一步搜索表明,进行了类似的操作将 gitk 合并到 git 中。 Junio C Hamano 在这里写到: http://www.mail -archive.com/[电子邮件受保护]/msg03395.html
The submodule approach is good if you want to maintain the project separately. However, if you really want to merge both projects into the same repository, then you have a bit more work to do.
The first thing would be to use
git filter-branch
to rewrite the names of everything in the second repository to be in the subdirectory where you would like them to end up. So instead offoo.c
,bar.html
, you would haveprojb/foo.c
andprojb/bar.html
.Then, you should be able to do something like the following:
The
git pull
will do agit fetch
followed by agit merge
. There should be no conflicts, if the repository you're pulling to does not yet have aprojb/
directory.Further searching indicates that something similar was done to merge
gitk
intogit
. Junio C Hamano writes about it here: http://www.mail-archive.com/[email protected]/msg03395.htmlgit-subtree
很好,但它可能不是您想要的。例如,如果
projectA
是在 B 中创建的目录,则之后git 子树
,仅列出一个提交:合并。合并项目的提交针对不同的路径,因此它们不会显示。
Greg Hewgill 的答案最接近,尽管它实际上没有说明如何重写路径。
解决方案出奇地简单。
(1) 在A中,
注:这重写了历史;您可能需要先备份 A。
注意:如果您在文件名或路径中使用非 ascii 字符(或白色字符),则必须修改 sed 命令内的替代脚本。在这种情况下,由“ls-files -s”生成的记录内的文件位置以引号开头。
(2) 然后在 B 中运行
瞧!您在 B 中有一个
projectA
目录。如果您运行git log projectA
,您将看到来自 A 的所有提交。在我的例子中,我想要两个子目录,
projectA
和projectB
。在这种情况下,我也对 B 执行了步骤 (1)。git-subtree
is nice, but it is probably not the one you want.For example, if
projectA
is the directory created in B, aftergit subtree
,lists only one commit: the merge. The commits from the merged project are for different paths, so they don't show up.
Greg Hewgill's answer comes closest, although it doesn't actually say how to rewrite the paths.
The solution is surprisingly simple.
(1) In A,
Note: This rewrites history; you may want to first make a backup of A.
Note Bene: You have to modify the substitute script inside the sed command in the case that you use non-ascii characters (or white characters) in file names or path. In that case the file location inside a record produced by "ls-files -s" begins with quotation mark.
(2) Then in B, run
Voila! You have a
projectA
directory in B. If you rungit log projectA
, you will see all commits from A.In my case, I wanted two subdirectories,
projectA
andprojectB
. In that case, I did step (1) to B as well.如果两个存储库具有相同类型的文件(例如不同项目的两个 Rails 存储库),您可以将辅助存储库的数据提取到当前存储库:
然后将其合并到当前存储库:
如果您的 Git 版本小于 2.9,请删除 <代码>--允许不相关的历史。
此后,可能会发生冲突。例如,您可以使用 git mergetool 来解决它们。
kdiff3
可以单独使用键盘,因此读取代码时只需几分钟即可获得 5 个冲突文件。记得完成合并:
If both repositories have same kind of files (like two Rails repositories for different projects), you can fetch data of the secondary repository to your current repository:
and then merge it to current repository:
If your Git version is smaller than 2.9, remove
--allow-unrelated-histories
.After this, conflicts may occur. You can resolve them for example with
git mergetool
.kdiff3
can be used solely with keyboard, so 5 conflict file takes when reading the code just few minutes.Remember to finish the merge:
使用合并时我不断丢失历史记录,因此我最终使用了 rebase,因为在我的情况下,两个存储库足够不同,不会在每次提交时最终合并:
=>解决冲突,然后根据需要多次继续...
这样做会导致一个项目拥有来自 projA 的所有提交,然后是来自 projB 的提交
I kept losing history when using merge, so I ended up using rebase since in my case the two repositories are different enough not to end up merging at every commit:
=> resolve conflicts, then continue, as many times as needed...
Doing this leads to one project having all commits from projA followed by commits from projB
就我而言,我有一个
my-plugin
存储库和一个main-project
存储库,我想假装my-plugin
始终已在main-project
的plugins
子目录中开发。基本上,我重写了
my-plugin
存储库的历史,以便所有开发都发生在plugins/my-plugin
子目录中。然后,我将my-plugin
的开发历史添加到main-project
历史中,并将两棵树合并在一起。由于main-project
存储库中不存在plugins/my-plugin
目录,因此这是一个简单的无冲突合并。生成的存储库包含两个原始项目的所有历史记录,并且有两个根。TL;DR
长版本
首先,创建
my-plugin
存储库的副本,因为我们将重写此存储库的历史记录。现在,导航到
my-plugin
存储库的根目录,检查您的主分支(可能是master
),然后运行以下命令。当然,无论您的实际名称是什么,您都应该替换my-plugin
和plugins
。现在进行解释。
git filter-branch --tree-filter (...) HEAD
在每次可从HEAD
访问的提交上运行(...)
命令代码>.请注意,这直接对每次提交存储的数据进行操作,因此我们不必担心“工作目录”、“索引”、“暂存”等概念。如果您运行
filter-branch
命令失败,它会在.git
目录中留下一些文件,并在您下次尝试filter-branch
时使用code> 它将抱怨这一点,除非您向filter-branch
提供-f
选项。至于实际的命令,我没有太多运气让
bash
来做我想做的事情,所以我使用zsh -c
来制作zsh 执行命令。首先,我设置了
选项,它允许我使用 glob (extended_glob
选项,该选项在mv
命令中启用^(...)
语法,以及 < code>glob_dots^(...)
) 选择点文件(例如.gitignore
)。接下来,我使用
mkdir -p
命令同时创建plugins
和plugins/my-plugin
。最后,我使用
zsh
“负 glob”功能^(.git|plugins)
来匹配存储库根目录中除.git 之外的所有文件
和新创建的my-plugin
文件夹。 (此处可能不需要排除.git
,但尝试将目录移动到其自身中是一个错误。)在我的存储库中,初始提交不包含任何文件,因此
mv< /code> 命令在初始提交时返回错误(因为没有任何内容可以移动)。因此,我添加了一个
|| true
这样git filter-branch
就不会中止。--all
选项告诉filter-branch
重写存储库中所有分支的历史记录,以及额外的--< /code> 有必要告诉 git 将其解释为要重写的分支选项列表的一部分,而不是作为
filter-branch
本身的选项。现在,导航到您的
main-project
存储库并检查您想要合并到的任何分支。将my-plugin
存储库的本地副本(其历史记录已修改)添加为main-project
的远程版本:您的提交历史记录中现在将有两个不相关的树,您可以使用以下方式很好地可视化:
要合并它们,请使用:
请注意,在 2.9.0 之前的 Git 中,
--allow-unlated-histories
选项不存在。如果您使用的是这些版本之一,只需省略该选项即可:2.9.0 中还添加了--allow-unlated-histories
阻止的错误消息。 em>你不应该有任何合并冲突。如果这样做,则可能意味着
filter-branch
命令无法正常工作,或者main-project 中已经存在
。plugins/my-plugin
目录确保为任何未来的贡献者输入解释性提交消息,他们想知道黑客是如何创建具有两个根的存储库的。
您可以使用上面的 git log 命令可视化新的提交图,它应该有两个根提交。请注意,只有
master
分支会被合并。这意味着,如果您在其他my-plugin
分支上进行了重要工作并希望将其合并到main-project
树中,则应避免删除my-plugin
分支。 -plugin 远程,直到完成这些合并。如果不这样做,那么来自这些分支的提交仍将位于main-project
存储库中,但有些分支将无法访问并且容易受到最终垃圾回收的影响。 (此外,您必须通过 SHA 引用它们,因为删除远程会删除其远程跟踪分支。)或者,在合并了想要从
my-plugin
中保留的所有内容后,您可以使用以下方法远程删除my-plugin
:您现在可以安全地删除您更改了历史记录的
my-plugin
存储库的副本。就我而言,在合并完成并推送后,我还在真正的 my-plugin 存储库中添加了弃用通知。在 Mac OS X El Capitan 上使用
git --version 2.9.0
和zsh --version 5.2
进行了测试。您的里程可能会有所不同。参考文献:
In my case, I had a
my-plugin
repository and amain-project
repository, and I wanted to pretend thatmy-plugin
had always been developed in theplugins
subdirectory ofmain-project
.Basically, I rewrote the history of the
my-plugin
repository so that it appeared all development took place in theplugins/my-plugin
subdirectory. Then, I added the development history ofmy-plugin
into themain-project
history, and merged the two trees together. Since there was noplugins/my-plugin
directory already present in themain-project
repository, this was a trivial no-conflicts merge. The resulting repository contained all history from both original projects, and had two roots.TL;DR
Long version
First, create a copy of the
my-plugin
repository, because we're going to be rewriting the history of this repository.Now, navigate to the root of the
my-plugin
repository, check out your main branch (probablymaster
), and run the following command. Of course, you should substitute formy-plugin
andplugins
whatever your actual names are.Now for an explanation.
git filter-branch --tree-filter (...) HEAD
runs the(...)
command on every commit that is reachable fromHEAD
. Note that this operates directly on the data stored for each commit, so we don't have to worry about notions of "working directory", "index", "staging", and so on.If you run a
filter-branch
command that fails, it will leave behind some files in the.git
directory and the next time you tryfilter-branch
it will complain about this, unless you supply the-f
option tofilter-branch
.As for the actual command, I didn't have much luck getting
bash
to do what I wanted, so instead I usezsh -c
to makezsh
execute a command. First I set theextended_glob
option, which is what enables the^(...)
syntax in themv
command, as well as theglob_dots
option, which allows me to select dotfiles (such as.gitignore
) with a glob (^(...)
).Next, I use the
mkdir -p
command to create bothplugins
andplugins/my-plugin
at the same time.Finally, I use the
zsh
"negative glob" feature^(.git|plugins)
to match all files in the root directory of the repository except for.git
and the newly createdmy-plugin
folder. (Excluding.git
might not be necessary here, but trying to move a directory into itself is an error.)In my repository, the initial commit did not include any files, so the
mv
command returned an error on the initial commit (since nothing was available to move). Therefore, I added a|| true
so thatgit filter-branch
would not abort.The
--all
option tellsfilter-branch
to rewrite the history for all branches in the repository, and the extra--
is necessary to tellgit
to interpret it as a part of the option list for branches to rewrite, instead of as an option tofilter-branch
itself.Now, navigate to your
main-project
repository and check out whatever branch you want to merge into. Add your local copy of themy-plugin
repository (with its history modified) as a remote ofmain-project
with:You will now have two unrelated trees in your commit history, which you can visualize nicely using:
To merge them, use:
Note that in pre-2.9.0 Git, the
--allow-unrelated-histories
option does not exist. If you are using one of these versions, just omit the option: the error message that--allow-unrelated-histories
prevents was also added in 2.9.0.You should not have any merge conflicts. If you do, it probably means that either the
filter-branch
command did not work correctly or there was already aplugins/my-plugin
directory inmain-project
.Make sure to enter an explanatory commit message for any future contributors wondering what hackery was going on to make a repository with two roots.
You can visualize the new commit graph, which should have two root commits, using the above
git log
command. Note that only themaster
branch will be merged. This means that if you have important work on othermy-plugin
branches that you want to merge into themain-project
tree, you should refrain from deleting themy-plugin
remote until you have done these merges. If you don't, then the commits from those branches will still be in themain-project
repository, but some will be unreachable and susceptible to eventual garbage collection. (Also, you will have to refer to them by SHA, because deleting a remote removes its remote-tracking branches.)Optionally, after you have merged everything you want to keep from
my-plugin
, you can remove themy-plugin
remote using:You can now safely delete the copy of the
my-plugin
repository whose history you changed. In my case, I also added a deprecation notice to the realmy-plugin
repository after the merge was complete and pushed.Tested on Mac OS X El Capitan with
git --version 2.9.0
andzsh --version 5.2
. Your mileage may vary.References:
几天来我一直在尝试做同样的事情,我使用的是 git 2.7.2。子树不保留历史记录。
如果您不再使用旧项目,可以使用此方法。
我建议你先去B分行,然后在分行工作。
以下是不分支的步骤:
如果您现在记录子目录 A 中的任何文件,您将获得完整的历史记录
这是帮助我执行此操作的帖子:
http://saintgimp.org/2013/01/22/merging-two-git-repositories-into -one-repository-without-losing-file-history/
I've been trying to do the same thing for days, I am using git 2.7.2. Subtree does not preserve the history.
You can use this method if you will not be using the old project again.
I would suggest that you branch B first and work in the branch.
Here are the steps without branching:
If you now log any of the files in subdir A you will get the full history
This was the post that help me do this:
http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/
如果您想将存储库 B 中的分支中的文件放入存储库 A 的子树中,并且也保留历史,请继续阅读。 (在下面的示例中,我假设我们希望将存储库 B 的主分支合并到存储库 A 的主分支中。)
在存储库 A 中,首先执行以下操作以使存储库 B 可用:
现在我们创建一个全新的分支(只有一次提交) )在存储库 A 中,我们称之为
new_b_root
。生成的提交将包含在存储库 B 的 master 分支的第一次提交中提交的文件,但放入名为path/to/b-files/
的子目录中。说明:checkout 命令的
--orphan
选项从 A 的 master 分支中检出文件,但不创建任何提交。我们可以选择任何提交,因为接下来我们无论如何都会清除所有文件。然后,在尚未提交的情况下 (-n
),我们从 B 的 master 分支中挑选第一个提交。 (cherry-pick 保留了原始提交消息,而直接签出似乎无法做到这一点。)然后我们创建子树,将存储库 B 中的所有文件放入其中。然后,我们必须移动在存储库 B 中引入的所有文件。樱桃挑选到子树。在上面的示例中,只有一个README
文件需要移动。然后我们提交 B-repo 根提交,同时我们还保留原始提交的时间戳。现在,我们将在新创建的
new_b_root
之上创建一个新的B/master
分支。我们将新分支称为b
:现在,我们将
b
分支合并到A/master
:最后,您可以删除
B
远程和临时分支:最终图表将具有如下结构:
If you want to put the files from a branch in repo B in a subtree of repo A and also preserve the history, keep reading. (In the example below, I am assuming that we want repo B's master branch merged into repo A's master branch.)
In repo A, first do the following to make repo B available:
Now we create a brand new branch (with only one commit) in repo A that we call
new_b_root
. The resulting commit will have the files that were committed in the first commit of repo B's master branch but put in a subdirectory calledpath/to/b-files/
.Explanation: The
--orphan
option to the checkout command checks out the files from A's master branch but doesn't create any commit. We could have selected any commit because next we clear out all the files anyway. Then, without committing yet (-n
), we cherry-pick the first commit from B's master branch. (The cherry-pick preserves the original commit message which a straight checkout doesn't seem to do.) Then we create the subtree where we want to put all files from repo B. We then have to move all files that were introduced in the cherry-pick to the subtree. In the example above, there's only aREADME
file to move. Then we commit our B-repo root commit, and, at the same time, we also preserve the timestamp of the original commit.Now, we'll create a new
B/master
branch on top of the newly creatednew_b_root
. We call the new branchb
:Now, we merge our
b
branch intoA/master
:Finally, you can remove the
B
remote and temporary branches:The final graph will have a structure like this:
我在这里收集了有关 StackOverFlow 等的大量信息,并设法将一个脚本放在一起,为我解决了问题。
需要注意的是,它只考虑每个存储库的“开发”分支,并将其合并到全新存储库中的单独目录中。
标签和其他分支将被忽略 - 这可能不是您想要的。
该脚本甚至可以处理功能分支和标签 - 在新项目中重命名它们,以便您知道它们来自哪里。
您还可以从 http://paste.ubuntu.com/11732805 获取它。
首先创建一个文件,其中包含每个存储库的 URL,例如:
然后调用脚本,给出项目名称和脚本路径:
脚本本身有很多注释,应该解释它的作用。
I have gathered a lot of information here on Stack OverFlow, etc., and have manage to put a script together which solves the problem for me.
The caveat is that it only takes into account the 'develop' branch of each repository and merges it into a separate directory in a completely new repository.
Tags and other branches are ignored - this might not be what you want.
The script even handles feature branches and tags - renaming them in the new project so you know where they came from.
You can also get it from http://paste.ubuntu.com/11732805
First create a file with the URL to each repository, e.g.:
Then call the script giving a name of the project and the path to the script:
The script itself has a lot of comments which should explain what it does.
我知道这已经是很久以后的事了,但我对在这里找到的其他答案并不满意,所以我写了这个:
I know it's long after the fact, but I wasn't happy with the other answers I found here, so I wrote this:
如果您尝试简单地将两个存储库粘合在一起,则子模块和子树合并是错误的工具,因为它们不会保留所有文件历史记录(正如人们在其他答案中指出的那样)。请参阅此处的答案,了解简单而正确的方法。
If you're trying to simply glue two repositories together, submodules and subtree merges are the wrong tool to use because they don't preserve all of the file history (as people have noted on other answers). See this answer here for the simple and correct way to do this.
我遇到了类似的挑战,但就我而言,我们在存储库 A 中开发了一个版本的代码库,然后将其克隆到新的存储库(存储库 B)中,以用于新版本的产品。修复了存储库 A 中的一些错误后,我们需要将更改修改到存储库 B 中。最终执行了以下操作:
很不错:)
I had a similar challenge, but in my case, we had developed one version of the codebase in repo A, then cloned that into a new repo, repo B, for the new version of the product. After fixing some bugs in repo A, we needed to FI the changes into repo B. Ended up doing the following:
Worked a treat :)
合并 2 个存储库
Merging 2 repos
与 @Smar 类似,但使用文件系统路径,在 PRIMARY 和 SECONDARY 中设置:
然后手动合并。
(改编自 Anar Manafov 的帖子 )
Similar to @Smar but uses file system paths, set in PRIMARY and SECONDARY:
Then you manually merge.
(adapted from post by Anar Manafov)
当您想要在单个提交中合并三个或更多项目时,请执行其他答案中描述的步骤(
remote add -f
、merge
)。然后,(软)将索引重置为旧头(没有发生合并)。添加所有文件 (git add -A
) 并提交它们(消息“将项目 A、B、C 和 D 合并为一个项目)。这现在是 master 的提交 ID。现在,创建
.git/info/grafts
包含以下内容:运行
git filter-branch -- head^..head head^2..head head^3..head
如果。如果您有三个以上的分支,只需添加与分支数量一样多的head^n..head
即可。要更新标签,请附加--tag-name-filter cat
。不要总是添加它,因为这可能会导致重写某些提交。有关详细信息,请参阅 filter-branch 的手册页,搜索“grafts”。现在,您的最后一次提交已关联正确的父级。
When you want to merge three or more projects in a single commit, do the steps as described in the other answers (
remote add -f
,merge
). Then, (soft) reset the index to old head (where no merge happened). Add all files (git add -A
) and commit them (message "Merging projects A, B, C, and D into one project). This is now the commit-id of master.Now, create
.git/info/grafts
with following content:Run
git filter-branch -- head^..head head^2..head head^3..head
. If you have more than three branches, just add as muchhead^n..head
as you have branches. To update tags, append--tag-name-filter cat
. Do not always add that, because this might cause a rewrite of some commits. For details see man page of filter-branch, search for "grafts".Now, your last commit has the right parents associated.
要将 A 合并到 B 中:
1) 在项目 A 中
2) 在项目 B 中
在此分支中执行您需要执行的所有操作并提交它们。
C) 然后回到 master 并在两个分支之间进行经典合并:
To merge a A within B:
1) In the project A
2) In the project B
In this branch do all operations you need to do and commit them.
C) Then back to the master and a classical merge between the two branches:
https://github.com/hraban/tomono 作为基于脚本的解决方案的另一个提及。
我不是作者,但使用了它并且它完成了工作。
一个积极的方面是您可以将所有分支和所有历史记录放入最终存储库中。对于我的存储库(存储库中没有重复的文件夹 - 实际上,它们来自 tfs2git 迁移),没有冲突,一切都是自动化运行的。
它主要用于(参见名称)创建 monorepos。
对于Windows用户:git bash可以执行.sh文件。它带有标准的 git 安装。
https://github.com/hraban/tomono as another mention of a script-based solution.
I am not the author but used it and it does the job.
One positive aspect is that you get all the branches and all the history into the final repo. For my repos (no duplicate folders in repos - actually, they came out of tfs2git migration) there were no conflicts and everything ran automated.
It is mainly used (see name) to create monorepos.
For Windows users: git bash can execute the .sh file. It comes with the standard git installation.
Google 使用 Copybara 工具来处理更复杂的用例 - https://github.com/google/copybara< /a>
There is a Copybara tool used by Google for more complex use cases - https://github.com/google/copybara
我稍微手动合并项目,这使我可以避免需要处理合并冲突。
首先,根据需要从其他项目中复制文件。
下一次拉入历史记录
告诉 git 合并到上次获取的历史记录中,
现在提交,但是您通常会提交
I merge projects slightly manually, which allows me to avoid needing to deal with merge conflicts.
first, copy in the files from the other project however you want them.
next pull in the history
tell git to merge in the history of last fetched thing
now commit however you normally would commit
我今天必须按如下方式解决它:
项目 A 位于 bitbucket 中,项目 B 位于代码提交中.. 两者是相同的项目,但必须合并从 A 到 B 的更改。(技巧是在项目 A 中创建相同名称的分支,与项目 B 中的相同)
I had to solve it as follows today:
Project A was in bitbucket and Project B was in code commit .. both are the same projects but had to merge changes from A to B. (The trick is to create the same name branch in Project A, same as in Project B)
该功能将远程仓库克隆到本地仓库目录,合并后所有提交将被保存,
git log
将显示原始提交和正确的路径:如何使用:
如果做一点更改,您甚至可以将合并的存储库的文件/目录移动到不同的路径,例如:
通知
路径通过
sed
替换,因此请确保合并后它移动到正确的路径中。--allow-unlated-histories
参数仅自 git >= 2.9 起存在。This function will clone remote repo into local repo dir, after merging all commits will be saved,
git log
will be show the original commits and proper paths:How to use:
If make a little changes you can even move files/dirs of merged repo into different paths, for example:
Notices
Paths replaces via
sed
, so make sure it moved in proper paths after merging.The
--allow-unrelated-histories
parameter only exists since git >= 2.9.除了使用
远程添加
的所有答案 ->获取
->merge
策略:如果您想保留其他存储库中的标签,但又不想将它们全部溢出到公共命名空间中(并且可能会发生冲突),您可能需要稍微更改一下 fetch 命令:第一个命令照常获取所有分支,但省略附加到提交的标签,第二个命令也省略通常的标签获取机制(
git help fetch
了解更多),并获取映射它们的所有标签使用 git 的 refspec 功能将X
转换为other_repo/X
。引用(分支、标签)只是 git 中的文件,您可以使用目录进行命名空间。上面的两个命令将按原样保留第一个存储库中的标签,而另一个存储库中的标签将以
other_repo/
为前缀。操作完成后,最好删除另一个远程存储库,这样您就不需要不要意外地以正常方式获取标签并弄乱。
In addition to all the answers using
remote add
->fetch
->merge
strategy: if you want to preserve tags from the other repository but don't want to spill them all into a common namespace (and possibly get collisions) you might want to change the fetch command a little bit:First command fetches all the branches as usual, but omits tags attached to commits, the second one also omits the usual tag fetching mechanism (
git help fetch
for more), and fetches all the tags mapping them fromX
toother_repo/X
using git's refspec functionality.References (branches, tags) are just files in git, and you can use directories for namespacing. The two commands above will preserve tags from the first repository as-is, and the ones from the other one will be prefixed with
other_repo/
After the operation it's best to remove the other remote, so you don't accidentally fetch the tags the normal way and make a mess.
给定命令是我建议的最佳解决方案。
Given command is the best possible solution I suggest.
我想将一个小项目移动到一个大项目的子目录中。由于我的小项目没有很多提交,因此我使用了 git format-patch --output-directory /path/to/patch-dir 。然后在更大的项目中,我使用了 git am --directory=dir/in/project /path/to/patch-dir/* 。
这感觉比过滤器分支没那么可怕,而且更干净。当然,它可能并不适用于所有情况。
I wanted to move a small project to a subdirectory of a larger one. Since my small project did not have many commits, I used
git format-patch --output-directory /path/to/patch-dir
. Then on the larger project, I usedgit am --directory=dir/in/project /path/to/patch-dir/*
.This feels way less scary and way more cleaner than a filter-branch. Granted, it may not be applicable to all cases.
注意:此解决方案可能会推送不需要的 LFS 二进制文件(合并的祖先提交未使用这些二进制文件),因此您可能需要改进此解决方案。
完成良好的 Andresch Serj 的回答,在
project-a
使用Git LFS
以便能够推送的情况下我必须首先执行以下操作:在我的情况下,这修复了尝试将合并结果合并到项目克隆目录中时遇到的错误:
以及
Note: this solution may push uneeded LFS binaries (that are not used by the merge's ancestor commits), so you might want to improve this solution.
To complete the good answer by Andresch Serj in the case the
project-a
usesGit LFS
in order to be able to push I had to first do:In my case this fixed the errors I got when attempting to
git push
the merge result in project-a clone directory:and