请考虑以下情形:
我在自己的Git仓库中开发了一个小型实验项目A.它现在已经成熟,我希望A成为更大的项目B的一部分,它有自己的大型存储库。我现在想添加A作为B的子目录。
如何将A合并到B中,而不会丢失任何一方的历史记录?
请考虑以下情形:
我在自己的Git仓库中开发了一个小型实验项目A.它现在已经成熟,我希望A成为更大的项目B的一部分,它有自己的大型存储库。我现在想添加A作为B的子目录。
如何将A合并到B中,而不会丢失任何一方的历史记录?
可以将另一个存储库的单个分支轻松放置在保留其历史记录的子目录下。例如:
git subtree add --prefix=rails git://github.com/rails/rails.git master
这将显示为单个提交,其中Rails主分支的所有文件都添加到“rails”目录中。 但是,commit的标题包含对旧历史树的引用:
从提交添加'rails /'
<rev>
哪里 <rev>
是SHA-1提交哈希。你仍然可以看到历史,归咎于一些变化。
git log <rev>
git blame <rev> -- README.md
请注意,您无法从此处看到目录前缀,因为这是一个完整的旧分支。 您应该将此视为通常的文件移动提交:到达时需要额外的跳转。
# finishes with all files added at once commit
git log rails/README.md
# then continue from original tree
git log <rev> -- README.md
有更复杂的解决方案,例如手动执行此操作或重写历史记录,如其他答案中所述。
git-subtree命令是官方git-contrib的一部分,一些数据包管理器默认安装它(OS X Homebrew)。 但除了git之外,您可能需要自己安装它。
如果你想合并 project-a
成 project-b
:
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a
取自: git合并不同的存储库?
这种方法对我来说效果很好,它更短,在我看来更清洁。
注意: 该 --allow-unrelated-histories
参数仅存在,因为git> = 2.9。看到 Git - git merge Documentation / --allow-unrelated-histories
这有两种可能的解决方案:
将存储库A复制到较大项目B中的单独目录中,或者(可能更好)将存储库A克隆到项目B中的子目录中。然后使用 git子模块 使这个存储库成为一个 子模块 存储库B.
对于松散耦合的存储库来说,这是一个很好的解决方案,其中存储库A中的开发仍在继续,并且开发的主要部分是A中的单独独立开发。 SubmoduleSupport 和 GitSubmoduleTutorial Git Wiki上的页面。
您可以使用存储库将存储库A合并到项目B的子目录中 子树合并 战略。这在中描述 子树合并和你 作者:Markus Prinz。
git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master
(选项 --allow-unrelated-histories
Git> = 2.9.0需要。)
或者你可以使用 git子树 工具(GitHub上的存储库由apenwarr(Avery Pennarun)在他的博客文章中宣布 Git子模块的新替代方案:git子树。
我认为在你的情况下(A是大项目B的一部分),正确的解决方案是使用 子树合并
如果要单独维护项目,子模块方法很好。但是,如果您真的想将两个项目合并到同一个存储库中,那么您还需要做更多的工作。
第一件事就是使用 git filter-branch
将第二个存储库中所有内容的名称重写到您希望它们结束的子目录中。而不是 foo.c
, bar.html
, 你将会拥有 projb/foo.c
和 projb/bar.html
。
然后,您应该能够执行以下操作:
git remote add projb [wherever]
git pull projb
该 git pull
会做的 git fetch
接下来是 git merge
。如果你要提取的存储库还没有,那么应该没有冲突 projb/
目录。
进一步搜索表明进行了类似的合并 gitk
成 git
。 Junio C Hamano在这里写道: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html
git-subtree
很好,但它可能不是你想要的那个。
例如,如果 projectA
是在B之后创建的目录 git subtree
,
git log projectA
名单 只有一个 提交:合并。合并项目的提交是针对不同的路径,因此它们不会显示。
Greg Hewgill的答案最接近,尽管它实际上没有说明如何改写路径。
解决方案非常简单。
(1)在A中,
PREFIX=projectA #adjust this
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$PREFIX"'/," |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD
注意:这会重写历史记录,因此如果您打算继续使用此回购A,您可能希望首先克隆(复制)它的一次性副本。
(2)然后在B中,运行
git pull path/to/A
瞧!你有一个 projectA
B中的目录。如果你运行 git log projectA
,你会看到来自A的所有提交。
就我而言,我想要两个子目录, projectA
和 projectB
。在那种情况下,我也做了步骤(1)到B.
如果两个存储库都具有相同类型的文件(例如,针对不同项目的两个Rails存储库),则可以将辅助存储库的数据提取到当前存储库:
git fetch git://repository.url/repo.git master:branch_name
然后将其合并到当前存储库:
git merge --allow-unrelated-histories branch_name
如果您的Git版本小于2.9,请删除 --allow-unrelated-histories
。
在此之后,可能会发生冲突。您可以使用例如解决它们 git mergetool
。 kdiff3
可以单独使用键盘,因此只需几分钟读取代码即可获得5个冲突文件。
记得完成合并:
git commit
在使用merge时我一直在丢失历史记录,所以我最终使用了rebase,因为在我的情况下,这两个存储库是不同的,不会在每次提交时最终合并:
git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB
cd projB
git remote add projA ../projA/
git fetch projA
git rebase projA/master HEAD
=>解决冲突,然后根据需要继续多次......
git rebase --continue
这样做会导致一个项目具有projA的所有提交,然后是projB的提交
就我而言,我有一个 my-plugin
存储库和a main-project
存储库,我想假装 my-plugin
一直在发展 plugins
子目录 main-project
。
基本上,我重写了历史 my-plugin
存储库,以便它看起来所有的发展都发生在 plugins/my-plugin
子目录。然后,我添加了开发历史 my-plugin
进入 main-project
历史,并将两棵树合并在一起。既然没有 plugins/my-plugin
目录已存在于 main-project
存储库,这是一个简单的无冲突合并。生成的存储库包含两个原始项目的所有历史记录,并且有两个根。
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|my-plugin) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
首先,创建一个副本 my-plugin
存储库,因为我们将重写此存储库的历史记录。
现在,导航到。的根目录 my-plugin
存储库,检查你的主要分支(可能 master
),并运行以下命令。当然,你应该替代 my-plugin
和 plugins
无论你的名字是什么。
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|my-plugin) plugins/my-plugin || true)'" -- --all
现在来解释一下。 git filter-branch --tree-filter (...) HEAD
跑了 (...)
每个可从中访问的提交的命令 HEAD
。请注意,这直接对每个提交存储的数据进行操作,因此我们不必担心“工作目录”,“索引”,“暂存”等概念。
如果你跑了 filter-branch
命令失败,它会留下一些文件 .git
目录和下次你尝试 filter-branch
它会抱怨这个,除非你提供 -f
选项 filter-branch
。
至于实际的命令,我没有太多运气 bash
做我想做的事,所以我使用 zsh -c
制作 zsh
执行命令。首先我设置了 extended_glob
选项,这是什么使能 ^(...)
语法 mv
命令,以及 glob_dots
选项,允许我选择点文件(例如 .gitignore
)用glob(^(...)
)。
接下来,我用了 mkdir -p
命令创建两者 plugins
和 plugins/my-plugin
与此同时。
最后,我用了 zsh
“负面水珠”功能 ^(.git|my-plugin)
匹配存储库根目录中的所有文件,除了 .git
和新创建的 my-plugin
夹。 (不包括 .git
可能没有必要在这里,但尝试将目录移动到自身是一个错误。)
在我的存储库中,初始提交不包含任何文件,所以 mv
命令在初始提交时返回错误(因为没有可用的移动)。因此,我补充说 || true
以便 git filter-branch
不会中止。
该 --all
选项告诉 filter-branch
重写历史 所有 存储库中的分支,以及额外的 --
有必要告诉 git
将它解释为分支重写的选项列表的一部分,而不是作为选项 filter-branch
本身。
现在,导航到您的 main-project
存储库并检查要合并到的任何分支。添加您的本地副本 my-plugin
存储库(已修改其历史记录)作为远程 main-project
有:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
您现在将在提交历史记录中有两个不相关的树,您可以使用以下方法很好地可视化:
$ git log --color --graph --decorate --all
要合并它们,请使用:
$ git merge my-plugin/master --allow-unrelated-histories
请注意,在2.9.0之前的Git中 --allow-unrelated-histories
选项不存在。如果您使用这些版本之一,只需省略选项:错误消息 --allow-unrelated-histories
防止了 也 在2.9.0中添加。
您不应该有任何合并冲突。如果你这样做,那可能意味着要么 filter-branch
命令无法正常工作或已经有一个 plugins/my-plugin
目录 main-project
。
确保为任何未来的贡献者输入一个解释性提交消息,想知道hackery正在进行什么样的生成有两个根的存储库。
您可以使用上面的内容可视化新的提交图,该图应该有两个根提交 git log
命令。注意 只有 master
分支将合并。这意味着如果你在其他方面有重要的工作 my-plugin
要合并到的分支 main-project
树,你应该避免删除 my-plugin
远程,直到你完成这些合并。如果你不这样做,那些来自那些分支的提交仍然会在 main-project
存储库,但有些将无法访问,并且易受最终垃圾回收的影响。 (此外,您必须通过SHA引用它们,因为删除远程会删除其远程跟踪分支。)
(可选)在合并了要保留的所有内容之后 my-plugin
,你可以删除 my-plugin
远程使用:
$ git remote remove my-plugin
您现在可以安全地删除该副本 my-plugin
您更改历史的存储库。就我而言,我还在真实中添加了弃用通知 my-plugin
合并完成并推送后的存储库。
在Mac OS X El Capitan上测试 git --version 2.9.0
和 zsh --version 5.2
。你的旅费可能会改变。
参考文献: