git subtree split 生成空提交是因为路径不匹配或历史中子目录未被完整提交;它从历史扫描含指定路径的提交重写新分支,而非提取当前工作区。

git subtree split 为什么只生成空提交
执行 git subtree split 后新分支里 git log 显示提交但 git show <commit></commit> 看不到文件,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 <new-url></new-url>,再显式推送:git push origin-new split-branch:main - 如果远程仓库已存在默认分支(如
master),把main换成对应名 - 推成功后,运行
git branch --set-upstream-to=origin-new/main split-branch,之后才能直接git push
git filter-repo 和 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、package.json 都不会带过去——这是设计行为,不是遗漏。
- 手动复制:从原项目根目录
cp .gitignore ../new-repo/,注意别复制错路径 - README 建议重写开头,加一句 “This is the
packages/core/module, extracted fromon 2026-04-26” - 如果是 npm 包,务必更新
package.json的name字段,否则npm publish会撞名 - 别漏掉
.npmignore或.prettierignore等配置文件,它们也属于根目录级,需单独补
实际操作中最容易卡住的点,是以为 git subtree split 像 cp 一样提取当前状态——它只看历史提交里有没有你写的那个路径,而且必须一字不差。跑一遍 git log --oneline --follow -- <your-path></your-path> 再动手,能省掉 80% 的排查时间。











