多仓库合并至 Mono-Repo 的实践指南

多仓库合并至 Mono-Repo 的实践指南

背景

在长期维护项目的过程中,我的代码资产分散在多个独立的 GitHub 仓库中,例如:

  • repo-a:项目 A(如前端组件库)
  • repo-b:项目 B(如后端服务)
  • repo-c:项目 C(如工具函数库)
  • repo-d:项目 D(如文档站点)

这种分散的仓库管理方式逐渐暴露出了以下问题:

  1. 维护成本高:同类逻辑在不同仓库中需要重复修改,难以同步。
  2. 历史查阅困难:查看完整的项目变更历史需要在多个仓库间切换。
  3. CI/CD 流程割裂:构建或发布时,需要拉取多个仓库,增加了流水线的复杂度。

为了解决这些痛点,我决定将这些分散的仓库 合并为一个 Mono-Repo(单体仓库),并确保 完整保留每个仓库原有的提交历史


方案选择

在 Git 中,合并多个仓库主要有以下几种方式:

  1. 直接复制文件:最简单的方式,但会完全丢失原有的提交历史(无法追溯是谁、在何时、修改了什么)。
  2. Git Submodule:保留了仓库的独立性,但父子仓库分离,Clone 和 CI 流程繁琐,体验较差。
  3. Git Subtree完美保留历史,同时将子仓库的代码物理合并到主仓库的指定目录下,管理起来更像是一个完整的仓库。

综合考虑,我选择了 Git Subtree 方案,因为它:

  • ✅ 支持完整保留每个子仓库的 commit 历史。
  • ✅ 代码直接存在于主仓库中,不依赖外部链接,拉取方便。
  • ✅ 支持双向同步(既可以获取子仓库更新,也可以推送修改回子仓库)。

实战步骤

1. 准备主仓库

首先,在 GitHub 创建一个新的空仓库(例如 my-mono-repo),然后将其克隆到本地并初始化:

# 克隆主仓库
git clone git@github.com:username/my-mono-repo.git
cd my-mono-repo

# 创建并切换到主分支
git checkout -b main

# 提交一个空的初始化 commit(必须步骤)
# 注意:Subtree 操作需要仓库中至少有一个 commit 才能进行
git commit --allow-empty -m "chore: initial commit for mono-repo"

2. 合并子仓库

使用 git subtree 将原有的仓库逐个合并进来。

以合并 repo-a 仓库为例:

# 1. 添加原仓库为远程地址(别名设为 repo-a-remote)
git remote add repo-a-remote git@github.com:username/repo-a.git

# 2. 拉取远程仓库数据
git fetch repo-a-remote

# 3. 使用 subtree 将其合并到指定的子目录(例如 packages/project-a)
# --prefix 指定目录路径
# repo-a-remote/main 指定远程仓库的分支
git subtree add --prefix=packages/project-a repo-a-remote main

重复上述步骤合并其他仓库:

# 合并第二个仓库
git remote add repo-b-remote git@github.com:username/repo-b.git
git fetch repo-b-remote
git subtree add --prefix=packages/project-b repo-b-remote main

# 合并第三个仓库
git remote add repo-c-remote git@github.com:username/repo-c.git
git fetch repo-c-remote
git subtree add --prefix=packages/project-c repo-c-remote main

# 合并第四个仓库
git remote add repo-d-remote git@github.com:username/repo-d.git
git fetch repo-d-remote
git subtree add --prefix=packages/project-d repo-d-remote main

注意:如果原仓库的主分支名为 master 而非 main,请将命令中的 main 替换为 master

3. 验证历史

合并完成后,我们可以检查一下提交历史,确认是否包含了原子仓库的记录:

# 查看提交日志
git log --graph --oneline --all

或者直接查看具体文件的历史,你会发现原有的 commit 信息、作者和时间都完整保留了下来。

4. 推送到远程仓库

确认无误后,将合并后的完整仓库推送到 GitHub:

git push -u origin main

至此,一个新的 Mono-Repo 就建立完成了。


成果展示

最终的仓库目录结构如下所示:

my-mono-repo/
├── README.md
├── packages/
│   ├── project-a/      <-- 原 repo-a 仓库内容
│   ├── project-b/      <-- 原 repo-b 仓库内容
│   ├── project-c/      <-- 原 repo-c 仓库内容
│   └── project-d/      <-- 原 repo-d 仓库内容
└── scripts/            <-- 统一管理的构建脚本

Mono-Repo 的优势:

  • 统一视图:所有相关代码在一个仓库中,便于全局搜索和重构。
  • 简化流程:统一管理 README、依赖和构建脚本,Release 发布更简单。
  • 灵活独立:原子仓库可以选择保留、归档甚至删除(如果确定不再独立维护),而 Mono-Repo 已经拥有了完整数据的副本。

总结

通过这次重构,我深刻体会到了 Git Subtree 在处理仓库合并时的强大能力。它不仅解决了代码分散管理的问题,还完美地解决了“保留历史”这一核心诉求。

现在,我们可以更专注于代码本身的编写,而不必在多个仓库间疲于奔命。

Tip:在执行此类操作前,强烈建议先备份所有原子仓库,或在本地进行充分演练,防止误操作导致数据丢失。


参考资料

💬 评论 0

加载评论中...

输入关键词开始搜索

欢迎回来

登录以继续使用 Fan's Docs

还没有账号?立即注册

扫码分享

扫码分享

打开APP扫一扫,分享给好友