就我而言,我有一个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
$ 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|plugins) 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|plugins) 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 选项,它允许我选择带有glob 的点文件(例如.gitignore) (^(...))。
接下来,我使用mkdir -p 命令同时创建plugins 和plugins/my-plugin。
最后,我使用zsh“负glob”功能^(.git|plugins)来匹配存储库根目录中除.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 命令无法正常工作,或者main-project 中已经存在plugins/my-plugin 目录。
确保为任何未来的贡献者输入解释性提交消息,以便他们想知道黑客正在做什么来创建具有两个根的存储库。
您可以使用上面的git log 命令可视化新的提交图,它应该有两个根提交。请注意,只会合并 master 分支。这意味着,如果您对要合并到 main-project 树中的其他 my-plugin 分支有重要工作,则在完成这些合并之前,您应该避免删除 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 进行测试。您的里程可能会有所不同。
参考资料: