把嵌套 clone 变成主仓库源码:一次 Git 归属边界清理

个人工具仓库里偶尔会放一些从开源项目改来的小工具。

第一版通常很随手:看到一个 MCP server、脚本工具或 demo 仓库能解决问题,先 git clone 到自己的 tools/ 目录里,改两行让它跑起来。等这个工具真的留下来,问题就变了:它已经是自己仓库的一部分,但目录里还保留着原仓库的 .git,IDE 和 Git 工具会继续把它识别成另一个项目。

这时如果主仓库只想推到自己的 Gitee 远端,就要把「源码内容」和「原仓库历史」拆开处理。源码可以留下,嵌套仓库的 Git 元数据要移走;主仓库的忽略规则也要跟着改,否则文件仍然不会被接管。

先确认主仓库到底推到哪里

清理前先看顶层仓库的 remote 和当前分支。这个步骤能避免把 IDE 里看到的 GitHub remote 误认为主仓库的提交目标。

git remote -v
git branch -vv
git status --short --branch

如果输出里只有自己的主 remote,例如:

origin  git@gitee.com:<owner>/<repo>.git (fetch)
origin  git@gitee.com:<owner>/<repo>.git (push)
* main  <sha> [origin/main] ...

顶层仓库本身就没有问题。后面看到的 GitHub 多半来自嵌套目录。

继续在主仓库里找 .git

find . -name .git -print

正常情况下只应该看到主仓库自己的 .git。如果还看到类似:

./tools/mcp-servers/tool-a/.git
./tools/mcp-servers/tool-b/.git
./.git

那两个工具目录就是独立 clone 进来的仓库。进入目录后再看 remote:

git -C tools/mcp-servers/tool-a remote -v
git -C tools/mcp-servers/tool-b remote -v

如果这里显示的是 GitHub 地址,说明 GitHub 关系来自这些工具目录,不来自主仓库。

区分 submodule 和普通嵌套 clone

看到嵌套 .git 后,还要确认它是不是 Git submodule。

submodule 是主仓库有意记录的外部仓库引用。普通嵌套 clone 更像是把另一个仓库直接复制进目录,只是 .git 也一起留了下来。两者处理方式不一样。

可以先看:

git submodule status --recursive
test -f .gitmodules && cat .gitmodules
git ls-files -s tools/mcp-servers/tool-a tools/mcp-servers/tool-b

如果没有 .gitmodulesgit submodule status 也没有输出,git ls-files -s 没有出现 mode 160000 的 gitlink,那它通常不是 submodule。它只是主仓库目录下的普通文件夹,里面额外带了自己的 .git

这种情况下,目标就很简单:保留文件,移走嵌套 .git

保留源码,移走嵌套 .git

我更习惯先移动到备份目录,而不是马上 rm -rf。原因很现实:嵌套仓库里可能有未推送的本地提交。删掉 .git 后文件内容还在,但那段独立仓库历史就不在原位置了。

处理前先看嵌套仓库有没有本地改动或 ahead 提交:

git -C tools/mcp-servers/tool-a status --short --branch
git -C tools/mcp-servers/tool-b status --short --branch
git -C tools/mcp-servers/tool-b log --oneline origin/main..HEAD

如果目标已经明确成「把当前文件内容当成自己的源码」,ahead 提交不一定要推回原上游。它只提醒你:这份文件内容可能已经包含本地改动,移走 .git 后应该由主仓库接管这些最终文件。

macOS / Linux shell 里可以这样做:

mkdir -p ../nested-git-backup

mv tools/mcp-servers/tool-a/.git ../nested-git-backup/tool-a.git
mv tools/mcp-servers/tool-b/.git ../nested-git-backup/tool-b.git

备份目录可以放到主仓库外面,避免主仓库把旧 .git 元数据也当成普通文件看见。确认没问题后,后续再手动删除备份。

让主仓库真正接管源码

只移走 .git 还不够。很多人第一版会在主仓库 .gitignore 里写一条:

tools/mcp-servers/

这会让主仓库继续忽略整个工具目录。嵌套 .git 移走后,源码虽然还在磁盘上,但主仓库不会跟踪它们。

更合理的规则是放开源码目录,只忽略本地依赖、虚拟环境、缓存和构建产物:

node_modules/
**/node_modules/

tools/mcp-servers/**/.venv/
tools/mcp-servers/**/__pycache__/
tools/mcp-servers/**/*.pyc
tools/mcp-servers/**/.pytest_cache/
tools/mcp-servers/**/.mypy_cache/
tools/mcp-servers/**/.coverage
tools/mcp-servers/**/coverage/
tools/mcp-servers/**/dist/
tools/mcp-servers/**/build/

这条边界很重要。主仓库要接管的是工具源码、README、测试、配置和 lockfile;不应该接管本机 .venvnode_modules、Python 字节码和构建输出。

改完后用下面几条命令验证:

find tools/mcp-servers -name .git -print
git ls-files --others --exclude-standard tools/mcp-servers | wc -l
git ls-files --others --exclude-standard tools/mcp-servers \
  | rg '(^|/)(node_modules|\.venv|__pycache__)(/|$)|\.pyc$'
git status --short --branch tools/mcp-servers .gitignore

第一条应该没有输出,说明嵌套 .git 已经移走。第二条会显示主仓库现在能看到多少个待接管文件。第三条如果没有输出,说明依赖和缓存没有混进待跟踪列表。最后一条用来复核即将提交的范围。

提交时只提交主仓库的变化

清理完成后,主仓库里通常会出现两类变化:

  • .gitignore 的规则调整。
  • tools/mcp-servers/... 下的一批新增源码文件。

可以按路径显式加入,避免把别的现场改动一起带进去:

git add .gitignore tools/mcp-servers
git status --short --branch

如果工作区里本来就有别的暂存或未暂存改动,提交前要单独看一眼。尤其是 AD 这种「索引里新增、工作区里又删除」的状态,很容易把一次仓库归属清理和另一件未完成工作混在同一个 commit 里。

确认范围后再提交:

git commit -m "整理嵌套工具源码归属"
git push origin main

这时 origin 仍然是主仓库自己的 Gitee 地址。原来两个工具目录里的 GitHub remote 已经随嵌套 .git 一起离开当前源码树,后续 IDE 也不会再把它们当成两个额外 Git 仓库。

总结

这类清理真正要处理的是归属边界。

  • 先用 git remote -vgit branch -vv 确认主仓库的提交目标,不要把嵌套目录的 remote 误认为顶层 remote。
  • 再用 find . -name .git -print 找出嵌套仓库,并用 .gitmodulesgit submodule statusgit ls-files -s 排除 submodule 场景。
  • 确认只是普通嵌套 clone 后,移动嵌套 .git,保留源码内容。
  • 最后调整 .gitignore,让主仓库接管源码,同时继续忽略依赖、虚拟环境、缓存和构建产物。

从别的仓库抄工具源码没有问题,关键是后面要及时把它变成自己的工程资产。留下源码,切断不需要维护的远端关系,再由主仓库统一提交和备份,这样后续维护才不会被一堆无意保留的 Git 边界牵着走。