git subtree split 生成空提交的那些坑
最近处理一个项目时,发现不少人卡在 git subtree split 这个命令上。明明操作看起来没问题,结果新分支里 git log 有记录,但 git show 却看不到文件,tree 是空的——这不是 bug,真的只是路径没对好,或者历史不匹配。
路径问题:最隐蔽的“凶手”
执行 git subtree split 后,新分支里 git log 有记录但 git show 看不到文件,tree 为空——这不是 bug,是路径没对上或历史不匹配。
--prefix必须以斜杠结尾,比如--prefix=src/lib/;写成src/lib或src/lib/(少斜杠)都会静默失败- 路径区分大小写,且必须和历史中真实出现的路径完全一致:如果某次提交里是
Src/utils/,写src/utils/就找不到 - 它扫描的是「该路径在哪些提交中被修改过」,不是提取当前工作区。先运行
git log --oneline --follow -- src/lib/确认这个路径真有连续提交历史 - 如果目录是后期手动拷贝进来的(没
git add过),需要先补一次全量提交:git add src/lib && git commit -m "feat: initial import"
拆分后 push 到新远程仓库被拒绝:non-fast-forward
第一次推送到全新远程仓库时,git push origin main 报 ! [rejected] main -> main (non-fast-forward),不是权限问题,是本地分支没关联远程起点。
- 别用
git push -f强推,这会破坏后续同步基础 - 先
git remote add origin-new,再显式推送:git push origin-new split-branch:main - 如果远程仓库已存在默认分支(如
master),把main换成对应名 - 推成功后,运行
git branch --set-upstream-to=origin-new/main split-branch,之后才能直接git push
git filter-repo vs subtree split:选哪个
你要彻底断开和原仓库的关系、追求最干净的历史,git filter-repo 是更优解;如果只是临时切出一个带历史的子模块用于同步维护,subtree split 更轻量。
git filter-repo --path src/cli/:只保留该路径所有历史,其他全删,新仓库根目录就是src/cli/下的内容git filter-repo --subdirectory-filter src/cli/:把src/cli/提升为新仓库根目录(自动去掉前缀)git subtree split不重写 commit hash,适合后续还要用git subtree pull/push双向同步的场景git filter-repo会重写全部 commit ID,历史不可追溯回原仓库,但新仓库更“独立”
拆出来的仓库缺 .gitignore 和 README 怎么办
git subtree split 只处理指定 --prefix 下的文件,不会把根目录的 .gitignore 或 README.md 带过去——这其实是设计使然,不是 bug。
- 拆完后进新仓库目录,
cp /path/to/original/.gitignore .(注意别复制错路径) - 同理复制
README.md,但建议重写开头,注明这是从原项目packages/core/拆出的独立模块 - 别忘了补
package.json的"name"和"publishConfig"字段(如果是 npm 包),否则发布时会撞名 - 如果原项目用了 Lerna 或 Turborepo,拆出来后要删掉
lerna.json或turbo.json相关配置
实际操作中最容易被忽略的,是路径末尾斜杠和历史路径大小写的严格匹配——它不报错,只静默返回空结果,等你发现时已经浪费了半小时排查。
