想把一个大大的前端 Monorepo 里 project-1
、project-2
、project-3
、project-4
拆分成 4 个小仓库,却又担心历史提交和分支都丢了?别慌,今天带你轻松上手官方推荐的 git-filter-repo,一步一步拆出来,保留所有提交、分支、标签,堪称搬家般顺滑!
🚀 整体思路
- 干净克隆
先给仓库来个“Fresh Clone”,确保本地没半点脏数据。 - 循环拆分
按照packages/project-x/
逐个「抽取」目录,只保留对应子项目的提交,并把内容搬到根目录。 - 一次搞定所有分支 & 标签
用--refs refs/heads/* --refs refs/tags/*
(或简单的--all
)一次性处理,分支、标签、PR 都不掉链子。 - 推送新仓库
配好新 remote,就能把完整历史推上去,开开心心写新功能。
📦 前置准备
- 安装 git-filter-repo(替代
filter-branch
,靠谱又快)# macOS(Homebrew) brew install git-filter-repo # 或者 Python Pip pip install git-filter-repo
- 务必在「Fresh Clone」上操作,让
git-filter-repo
知道这是一次全新搬家。 - 强烈建议 先在离线或临时目录试跑一遍,别在生产库里玩命令。
- 如果你的 Monorepo 里还有子模块,得额外给子模块也跑一遍同样流程。
🔨 拆分单个项目示例
下面以 project-1
为例,其他项目同理:
1. Fresh Clone
git clone --no-local git@your.host:your-org/monorepo.git mono-split
cd mono-split
提示:
--no-local
可以防止 Git 在同机做本地快照拷贝,确保“真正”干净克隆。
2. 移除旧的 remote
git remote remove origin
我们要防止后续命令误推回原 Monorepo,先把旧的 origin 干掉。
3. 运行 git-filter-repo
git filter-repo \
--path packages/project-1/ \
--path-rename packages/project-1/:/ \
--refs refs/heads/* --refs refs/tags/* \
--force
--path
:只保留packages/project-1/
下的提交。--path-rename
:把这部分内容搬到新仓库的根目录。--refs refs/heads/* --refs refs/tags/*
:保证分支、标签都处理到。--force
:跳过 Fresh Clone 限制(确认自己在安全环境下再加)。
4. 配置新 remote & 推送
git remote add origin git@your.host:your-org/project-1.git
git push -u origin --all
git push origin --tags
--all
:把所有分支都推上去。--tags
:标签也别忘了。
5. 验收
git log --oneline # 看看提交历史
git branch -a && git tag # 确认分支和标签都齐活儿了
🛠️ 一脚本批量拆分(Node.js 版)
省事秘诀来了!把下面代码保存为 split-monorepo.js
,直接 node split-monorepo.js
跑起来:
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const MONO_REPO = 'git@your.host:your-org/monorepo.git';
const PROJECTS = ['project-1','project-2','project-3','project-4'];
function run(cmd, cwd) {
console.log(`\n> ${cmd}`);
execSync(cmd, { stdio:'inherit', cwd });
}
PROJECTS.forEach(proj => {
const dir = `split-${proj}`;
console.log(`\n✨ 拆分 ${proj} → ${dir}`);
// 干净开始
if (fs.existsSync(dir)) fs.rmSync(dir,{recursive:true,force:true});
run(`git clone --no-local ${MONO_REPO} ${dir}`, process.cwd());
const cwd = path.join(process.cwd(), dir);
run(`git remote remove origin`, cwd);
run([
'git filter-repo',
`--path packages/${proj}/`,
`--path-rename packages/${proj}/:/`,
'--refs refs/heads/* --refs refs/tags/*',
'--force'
].join(' '), cwd);
const newRepo = `git@your.host:your-org/${proj}.git`;
run(`git remote add origin ${newRepo}`, cwd);
run(`git push -u origin --all`, cwd);
run(`git push origin --tags`, cwd);
console.log(`✅ ${proj} 完成`);
});
console.log('\n🎉 全部拆完,OK!');
Tip: 给脚本加上执行权限:
chmod +x split-monorepo.js
💡 小贴士 & 常见坑
- 子模块:要拆的子模块里也得各自跑一遍这套流程。
- 演练验证:在测试环境先跑一遍,确认目录、提交、分支都没问题再上生产。
- 仓库备份:真的,备份!打个 tag、拷个 bundle,出了问题好回滚。
- CI/CD 别忘改:拆完之后记得把各子项目的流水线、文档链接、依赖配置都跟着更新。