Git Submodule Magic Scricts
[!IMPORTANT]
引言 · 为什么今天一定要学会 submodule?
在真实的软件世界里,我们很少只面对“一个仓库、一条分支、一套代码”的童话。
微服务、共享组件、跨团队 SDK、甚至不同语言的构建脚本,都可能散落在独立的 Git 仓库里。于是,“把别人的仓库嵌进我的仓库”成了日常刚需——
• 前端团队把 Design-System 当子模块挂在 web-app 里;
• 固件工程师把第三方驱动仓库塞进主芯片 SDK;
• DevOps 把 Terraform 模块仓库嵌进基础设施 mono-repo;
• 甚至一本用 Markdown 写的大部头技术书,也把每章示例代码独立成子仓库,方便读者单独克隆。
如果每次都手动复制粘贴,版本就会迅速失控:今天 A 仓库升级了 API,B 仓库却还停留在旧 ABI,连编译都过不了。
Git 给出的官方答案就是 submodule —— 让主仓库只保留“指针”,真实代码仍由子仓库自己管理,既解耦又能精确追踪版本。
然而 submodule 的 UX 常被吐槽“反人类”:
clone 后代码是空的、update 命令一长串、指针冲突看不懂、CI 拉不动……
本篇《把子模块驯成乖猫咪》正是在这样的背景下写成:
把踩坑经验翻译成一步步可复制的命令,把晦涩概念画成猫咪洗澡换毛的比喻,让你十分钟就能从“为什么”走到“怎么做”,再到“怎么做得优雅”。
带着“让跨仓库协作不再痛苦”的使命,我们开始吧。
🌊 《把子模块驯成乖猫咪:Git Submodule 从入门到精通的实战魔法书》 🐱🏍
—— 一条命令就让它更新,一行配置就能同步,一口气读完就能上手!
1️⃣ 概念速览
- 主仓库(Superproject):你的大项目。
 - 子模块(Submodule):存在于主仓库里,却指向另一个独立 Git 仓库的某个提交。
 - 指针文件:主仓库只记录子模块的“提交 SHA”,不记录代码本身。
 
一句话:子模块像猫咪,主仓库只是猫窝,猫咪自己会去别处洗澡换毛。
2️⃣ 环境准备
# 创建两个干净的练习仓库
mkdir ~/submodule-lab && cd ~/submodule-lab
# 1. 子仓库(library)
git init library
cd library
echo "# Library README" > README.md
git add . && git commit -m "Initial library commit"
cd ..
# 2. 主仓库(app)
git init app
cd app
echo "# App README" > README.md
git add . && git commit -m "Initial app commit"
3️⃣ 添加子模块:第一次把猫咪带回家
# 仍在 ~/submodule-lab/app
git submodule add ../library vendor/library   # 使用相对路径,方便本地演练
git status
# 会看到:
# new file:   .gitmodules
# new file:   vendor/library
git commit -m "Add library as submodule"
.gitmodules 文件内容:
[submodule "vendor/library"]
    path = vendor/library
    url = ../library
4️⃣ 克隆含子模块的仓库:别让猫咪跑丢
# 换台电脑/目录模拟
cd ~/submodule-lab
git clone app app-clone
cd app-clone
# 关键!
git submodule update --init --recursive
# 或一步到位:
git clone --recurse-submodules ../app app-clone-fast
5️⃣ 本地更新子模块:猫咪想换新毛
# 进入子模块目录
cd vendor/library
git checkout main           # 确保在正确分支
git pull origin main        # 拉取最新代码
# 或手动做修改
echo "new feature" > new.txt && git add . && git commit -m "Add new feature"
# 返回主仓库
cd ../..
# 查看主仓库状态
git diff --submodule
# 看到子模块指针从旧 SHA 变成新 SHA
git add vendor/library
git commit -m "Bump library to latest main"
6️⃣ 批量更新所有子模块:一键换毛
# 顶层目录
git submodule foreach 'git fetch origin && git checkout main && git pull origin main'
# 或者直接递归拉取
git submodule update --remote --merge
--remote:让 Git 读取.gitmodules中记录的跟踪分支并拉取。--merge/--rebase:决定如何整合上游变更。
7️⃣ 提交子模块指针变更:告诉主仓库“猫换毛了”
git status
git add .
git commit -m "Update all submodules to latest upstream"
git push
CI/CD 提示:如果主仓库有钩子,请确保子模块的 URL 对 CI 可见(SSH key 或 token)。
8️⃣ 进阶技巧
8.1 固定子模块到指定分支
.gitmodules 追加:
[submodule "vendor/library"]
    branch = stable
然后:
git submodule set-branch --branch stable vendor/library
git submodule update --remote
8.2 递归克隆/拉取 alias
~/.gitconfig 添加:
[alias]
    cloneall = clone --recurse-submodules
    pullall = !git pull && git submodule update --remote --recursive
8.3 子模块冲突排查
场景:主仓库 A 同事把子模块指到 SHA1,你指到 SHA2。
git pull --rebase          # 此时出现冲突
git mergetool              # 如果配置了子模块 diff 工具
# 或手动:
git checkout --theirs vendor/library   # 保留对方指针
git checkout --ours   vendor/library   # 保留我方指针
git add vendor/library && git rebase --continue
9️⃣ 一键脚本:懒人专属
保存为 update-submodules.sh:
#!/usr/bin/env bash
set -e
echo "🐱 Updating all submodules..."
git submodule update --init --recursive --remote --merge
git add .
git commit -m "chore: sync submodules $(date '+%F %T')" || true
git push
使用:
chmod +x update-submodules.sh
./update-submodules.sh
🔖 参考资料 & 彩蛋
- 
GitHub Cheatsheet:
https://github.github.io/training-kit/downloads/zh_CN/github-git-cheat-sheet/ - 
彩蛋:
运行git config --global diff.submodule log可以让git diff显示子模块的提交日志而非冷冰冰的 SHA,效果如下:Submodule vendor/library 9a3e2f0..c8d4b1a: > Add new feature > Fix typo 
🎉 恭喜!至此,你已掌握从“把猫咪领回家”到“给它一键洗澡”的全部魔法。
下次见,继续驯服更多 Git 神兽!
                    
                
                
            
        
浙公网安备 33010602011771号