git: 在两个 repo 之间 merge

事情是这样,有一个叫 scripts 的 git 库,里面放了一些所属不明的脚本。我希望把这些零散脚本管理起来,它们不属于任何一个项目,为每个脚本都建一个库又太小题大做,于是有了一个『脚本孤儿院』。

与此同时,另外有个项目在进行,到一定程度时,它需要引用前面脚本里的一个。我突然明白,那个脚本就是这个项目的一部分,它『找到组织』,该『认祖归宗』了!

这个背景其实不重要,就是提供一个具体的场景。不知道你有没有过在不同仓库调整文件历史的时候。

剪切,粘贴?

最简单的方法当然是,从原库 剪切 相关文件, 粘贴 到新库,然后两边分别提交变更。

确实,移交 文件所属 这件事完成了。但结果是,在新库历史里这些文件是凭空出现的,之前的提交历史都丢失了。

当然你可以在新库提交时,在 message 里提一下,这些文件原本是在哪个库,详细历史请参考该库历史。可如果有一天旧库不再维护呢?你无法保证两个库生命周期一致。

merge ?

怎么连带提交历史一起移动呢? Merge !

Merge 就是为这个目的存在的。

不过等等,merge 只能在 branch 之间进行吧,两个 repo 怎么做?

严格来说,是 commit 之间,branch 也好 tag 也罢,在 git 里最后都是指向 commit 的指针。

fetch & merge

当然可以,只要把要合并来源,当做远程库,获取到目标库生成一个远程分支,再合并即可。

假设要做的是 repo-fromrepo-target 的合并,按以下操作即可:

1
2
3
4
5
6
cd repo-target
# 不要忘了 git 支持的协议非常广泛,除了常用的http(s)和ssh,也支持文件夹作为远程库
git remote add other /local/path/to/repo-from
git fetch other
# 这里假定你要合并 master,别的分支改一下就好
git merge other/master

完成之后再 git remote rm other , 然后 git rebase 稍微调整一下提交历史就好。当然也可以 fetch 后直接选择 rebase,把需要的 commit 挑选出来。mergerebase 的用法这里不再赘述。

这样合并,会把完整历史引进来,但实际上你可能只要部分文件和相关的提交历史,所以需要挑选部分提交历史。

fast-export & fast-import

上述方法基本可以解决问题,非要挑剔的话:

  1. 要先 添加 远程库 再删掉
  2. 引入的历史可能太多

其实 git 自带了相关工具:

1
2
3
4
$ cd repo-from
$ git fast-export master~5..master | sed "s|refs/heads/master|refs/heads/other|" | (cd /path/to/repo-target && git fast-import)
$ cd repo-target
$ git merge other

通过 fast-export + pipe + fast-import 从一个库导入到另一个库。注意的地方有几个:

  1. 因为两边都是本地分支,所以为了避免重名,中间可以用 sed 改名
  2. fast-export 可以指定导入的范围,例如上面就是获取 master 最后5个commit。不过注意的是,这个只是限制了commit 历史,文件还是会整个分支(也就是最新那个 commit 的所有祖先 commit)都包含进来。文件的筛选就要在 merge 阶段做了。

内容就介绍到这了,留心细节并不复杂。一些具体的指令还有 commit 范围的表示,就不在本篇范围内了。

对于 Git 的使用,不该排斥甚至推荐用 TortoiseGit 和 SourceTree 这样的 GUI 工具,为常规操作节省时间。但也要了解底层的指令,可以做到更多更细致的操作。


知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议
本文为本人原创,采用知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议进行许可。
本作品可自由复制、传播及基于本作品进行演绎创作。如有以上需要,请留言告知,在文章开头明显位置加上署名(Jayce Chant)、原链接及许可协议信息,并明确指出修改(如有),不得用于商业用途。谢谢合作。
详情请点击查看协议具体内容。