1. 问题现象
在父仓库中,DeepSpeed/deepspeed_npu 和 DeepSpeed/deepspeed_npu_bak 这两个目录:
- 在 VS Code 的 Changes 里看起来像"两个目录变更"
git diff显示的是:
text
Subproject commit <commit-id>
Subproject commit <commit-id>-dirty
而不是普通文件的 diff。
同时执行:
bash
git submodule status
报错:
text
fatal: no submodule mapping found in .gitmodules for path 'DeepSpeed/deepspeed_npu'
并且检查发现:
bash
find DeepSpeed/deepspeed_npu -maxdepth 2 -name .git
find DeepSpeed/deepspeed_npu_bak -maxdepth 2 -name .git
都能找到各自内部的 .git。
2. 结论
这两个目录:
- 物理上是文件夹
- 内部各自又是一个独立 Git 仓库
- 在父仓库里被记录成了 gitlink
- 但又不是一个配置完整的正规 submodule
也就是说,它们当前更像:
嵌套 Git 仓库残留 / 半配置状态的伪 submodule
而不是一个正常维护的标准 submodule。
3. 什么是 gitlink
3.1 定义
gitlink 是 Git 里一种特殊条目,用来表示:
这个路径不是普通文件,也不是普通目录内容,而是"另一个 Git 仓库的某个提交指针"。
父仓库不会展开管理这个目录中的每个文件,而是只记录:
- 这个子目录对应的子仓库提交哈希(commit id)
3.2 在 Git 里的表现
普通文件常见模式:
100644:普通文件100755:可执行文件
而 gitlink 的模式是:
text
160000
所以如果执行:
bash
git ls-files --stage | grep deepspeed_npu
前面出现 160000,就说明这个路径在父仓库里是 gitlink。
3.3 为什么 git diff 会显示 Subproject commit
因为父仓库只记录:
- 子仓库指向哪个 commit
- 子仓库当前是不是 dirty
而不直接记录子仓库里的文件级变化。
所以 diff 才会显示:
text
Subproject commit ...
而不是普通文件 diff。
4. 什么是 -dirty
如果看到:
text
Subproject commit <hash>-dirty
意思是:
该子仓库内部有未提交修改。
也就是说:
deepspeed_npu内部改过文件但还没提交deepspeed_npu_bak内部也可能有未提交修改
父仓库只能感知"这个子仓库变脏了",但不会直接展示里面每个文件怎么改的。
5. 为什么 git submodule status 会报错
报错:
text
fatal: no submodule mapping found in .gitmodules for path 'DeepSpeed/deepspeed_npu'
说明:
- Git 索引里把这个路径当成了"像 submodule 的条目"
- 但
.gitmodules里找不到对应映射
这意味着:
- 它不是一个正规配置好的 submodule
- 很可能是历史上:
- 目录本来就是一个独立 Git 仓库
- 被父仓库错误地 add 成了 gitlink
- 或者曾经有
.gitmodules,后来被删了 - 或者从未用
git submodule add正规添加过
6. 当前状态的本质
当前仓库状态可以理解为:
text
hanayo_baseline/
└── DeepSpeed/
├── deepspeed_npu/ # 内部自己是一个 Git 仓库
└── deepspeed_npu_bak/ # 内部自己也是一个 Git 仓库
但是父仓库:
- 没有正确维护
.gitmodules - 却把它们记录成了 gitlink
因此它们表现得:
- 看起来像普通文件夹
- 实际上 Git 又不按普通目录处理
- VS Code / Git diff / commit 行为都显得"奇怪"
7. 一般建议:尽量不要在一个仓库里套子仓库
对于大多数项目来说:
一个仓库下面不要再嵌套子仓库,通常更省事。
7.1 原因
子仓库会带来额外复杂度:
- 看起来像文件夹,其实不是普通文件夹
- 提交要分两层
- clone / pull / checkout 容易出问题
- IDE 和 VS Code 展示不直观
- diff、commit、push 更容易让人困惑
7.2 什么时候才值得用 submodule
只有在下面这种场景才更适合:
- 这个目录确实是一个独立项目
- 有独立版本管理需求
- 需要被多个项目复用
- 需要精确锁定依赖的 commit
7.3 对当前场景的判断
deepspeed_npu 和 deepspeed_npu_bak 更像:
- 项目内部代码
- 调试用代码
- 备份 / 实验版本
尤其 *_bak 这种命名,更不像应该独立维护的正式子仓库。
所以更推荐:
deepspeed_npu作为普通目录deepspeed_npu_bak尽量不要长期放在主仓库里- 可以删除
- 或移到仓库外
- 或改用 Git 分支保留历史
8. 更推荐的处理方式:改成普通目录
如果希望父仓库统一管理这些代码,更推荐:
把嵌套 Git 仓库改成普通目录
也就是:
- 去掉父仓库里的 gitlink 记录
- 删除子目录内部自己的
.git - 重新把目录按普通文件夹加入父仓库
9. 安全处理流程
第 0 步:先备份
bash
cp -r DeepSpeed/deepspeed_npu /tmp/deepspeed_npu_backup
cp -r DeepSpeed/deepspeed_npu_bak /tmp/deepspeed_npu_bak_backup
第 1 步:确认它们在父仓库里是不是 gitlink
bash
git ls-files --stage | grep 'DeepSpeed/deepspeed_npu'
git ls-files --stage | grep 'DeepSpeed/deepspeed_npu_bak'
如果前面显示 160000,说明它们确实是 gitlink。
第 2 步:从父仓库索引里移除 gitlink(不删工作区内容)
bash
git rm --cached -f DeepSpeed/deepspeed_npu
git rm --cached -f DeepSpeed/deepspeed_npu_bak
第 3 步:删除子目录内部自己的 .git
bash
rm -rf DeepSpeed/deepspeed_npu/.git
rm -rf DeepSpeed/deepspeed_npu_bak/.git
第 4 步:如果 .gitmodules 没有实际内容,可以删掉
bash
rm -f .gitmodules
第 5 步:把它们当普通目录重新加入父仓库
bash
git add DeepSpeed/deepspeed_npu
git add DeepSpeed/deepspeed_npu_bak
第 6 步:检查结果
bash
git status
git diff --cached --stat
如果这时显示的是里面具体文件,而不是:
text
Subproject commit ...
就说明已经成功变成普通目录。
第 7 步:提交
bash
git commit -m "convert nested git repos to regular directories"
10. 一句话总结
关于当前问题
deepspeed_npu 和 deepspeed_npu_bak 不是标准 submodule,而是:
父仓库中残留的嵌套 Git 仓库 / gitlink 状态
关于 gitlink
gitlink 的本质是:
父仓库只记录"这里指向另一个 Git 仓库的某个提交",而不管理其内部文件。
关于实践建议
对当前项目,更推荐:
把这两个目录改成普通目录,由父仓库统一管理。