git-git submodule和git subtree的使用方式
Git Submodule
以下是使用 Git Submodule 的完整、标准、可操作的流程,涵盖从添加、克隆、更新到提交的全过程,适用于团队协作场景。
🧩 Git Submodule 完整操作流程
假设:
- 父仓库(主项目):
main-project
(你的主工程) - 子仓库(被嵌入项目):
shared-utils
(一个通用工具库,GitHub 地址:https://github.com/yourname/shared-utils.git
)
✅ 1. 在父仓库中添加子模块(首次添加)
# 步骤 1:确保父仓库在本地
git clone https://github.com/yourname/main-project.git# 进入父仓库目录
cd main-project# 添加子模块,指定路径(例如:libs/shared-utils)
git submodule add https://github.com/yourname/shared-utils.git libs/shared-utils
✅ 执行后:
libs/shared-utils/
目录被创建并克隆了子仓库代码。- 生成
.gitmodules
文件,记录子模块信息。- 暂存区会包含
.gitmodules
和libs/shared-utils
(作为一个 gitlink)。
.gitmodules
文件内容示例:
[submodule "shared-utils"]path = libs/shared-utilsurl = https://github.com/yourname/shared-utils.git
# 提交变更
git commit -m "feat: add shared-utils as submodule"
git push origin main
✅ 2. 克隆包含子模块的父仓库(新成员操作)
⚠️ 普通 git clone
不会自动拉取子模块内容!
# 方法一:克隆时递归拉取子模块(推荐)
git clone --recurse-submodules https://github.com/yourname/main-project.git
cd main-project
# 方法二:分步操作(适用于已克隆的仓库)
git clone https://github.com/yourname/main-project.git
cd main-project# 初始化子模块配置
git submodule init# 拉取子模块代码
git submodule update
✅ 此时
libs/shared-utils/
目录中已有代码。
✅ 3. 更新子模块(子项目有新提交)
场景:shared-utils
仓库更新了,你想在主项目中使用最新代码
# 进入子模块目录
cd libs/shared-utils# 拉取远程最新提交
git pull origin main
# 或切换分支
# git checkout dev && git pull origin dev
# 回到父仓库,提交子模块的新 commit 引用
cd ../..
git add libs/shared-utils
git commit -m "chore: update shared-utils to latest version"
🔁 父仓库记录的是子模块的 具体 commit ID,所以这一步是必须的。
✅ 4. 更新子模块到特定版本(如 tag 或分支)
cd libs/shared-utils# 切换到某个 tag
git checkout v1.2.0# 或切换到某个分支
git checkout release/v1.2# 拉取代码
git pull origin HEAD
# 回到父仓库,提交变更
cd ../..
git add libs/shared-utils
git commit -m "chore: update shared-utils to v1.2.0"
✅ 5. 子模块内修改并提交(较少见,但可能)
⚠️ 注意:子模块在父仓库中默认处于“游离 HEAD”状态。建议先切换到分支。
cd libs/shared-utils# 确保在分支上(避免游离 HEAD)
git checkout main# 做修改
echo "// 新功能" >> utils.js
git add utils.js
git commit -m "feat: add new helper function"
git push origin main
# 回到父仓库,更新引用
cd ../..
git add libs/shared-utils
git commit -m "chore: update shared-utils with new feature"
✅ 6. 删除子模块(如果不再需要)
# 1. 停用子模块
git submodule deinit -f libs/shared-utils# 2. 删除工作区和缓存中的子模块
git rm -f libs/shared-utils# 3. 删除子模块目录(.git 文件夹)
rm -rf .git/modules/libs/shared-utils# 4. 提交删除
git commit -m "chore: remove shared-utils submodule"
✅ 7. 批量更新所有子模块(如有多个)
# 拉取所有子模块的最新代码
git submodule update --remote --merge# 或递归更新(含嵌套子模块)
git submodule update --init --recursive
✅ 8. 查看子模块状态
# 查看子模块当前 commit 和状态
git submodule status# 输出示例:
# a1b2c3d libs/shared-utils (heads/main)
📌 最佳实践建议
建议 | 说明 |
---|---|
✅ 使用 --recurse-submodules 克隆 | 避免遗漏子模块 |
✅ 提交前确保子模块在正确分支 | 避免游离 HEAD |
✅ 文档化子模块用途 | 帮助新成员理解 |
✅ CI/CD 中配置递归克隆 | git clone --recurse-submodules |
⚠️ 避免嵌套过深 | 管理复杂度高 |
🚫 常见错误与解决
错误 | 解决方案 |
---|---|
Unable to find current revision in submodule | 运行 git submodule update --init --recursive |
子模块显示“modified”但无变更 | 检查子模块是否在游离 HEAD,或运行 git submodule update |
权限错误(SSH) | 确保 SSH 密钥配置正确,或改用 HTTPS |
✅ 总结
操作 | 命令 |
---|---|
添加子模块 | git submodule add <url> <path> |
克隆带子模块 | git clone --recurse-submodules <url> |
初始化子模块 | git submodule init && git submodule update |
更新子模块 | 进入目录 git pull ,父仓库 git add + commit |
删除子模块 | deinit → rm → rm .git/modules/... |
虽然 git submodule
有学习成本,但只要团队统一操作规范,它仍然是管理独立组件的一种有效方式。如果团队觉得太复杂,建议考虑 Monorepo 或 包管理 方案。
使用git submoudle提交后父工程包含的并不是子工程的代码而是子工程的一个文件连接,父工程如果提交的时候不想提交的只是子工程的一个链接想一起提交代码怎么办?
答案就是使用git subtree
这是一个非常关键的问题!
git subtree
而 git submodule
的设计本质就是:父仓库只记录子仓库的 URL 和 commit ID(即一个“链接”),而不是把子仓库的代码真正“融合”进来。
✅ 您真正想要的是:把子项目代码“融合”进父项目一起管理
有 两种主流方案 可以实现您想要的效果:
✅ 方案一:使用 git subtree
(推荐,满足您的需求)
git subtree
可以将另一个仓库的代码真正合并到当前目录,父仓库提交时,子项目的代码会作为普通文件一起被提交,不再只是一个链接。
🌰 举个例子:
# 将 shared-utils 仓库的代码合并到 vendor/shared-utils 目录
git subtree add --prefix=vendor/shared-utils \https://github.com/you/shared-utils.git main --squash
✅ 效果:
vendor/shared-utils/
目录下是 真实的代码文件。- 父仓库的提交历史中包含了这些代码。
- 克隆父仓库时,这些代码自动存在,无需额外操作。
后续更新子项目代码:
# 拉取子项目最新变更,并合并到父仓库
git subtree pull --prefix=vendor/shared-utils \https://github.com/you/shared-utils.git main --squash
推送修改回子项目(可选):
git subtree push --prefix=vendor/shared-utils \https://github.com/you/shared-utils.git feature/patch
✅ 优点:
- 代码真正融合,提交时一起提交。
- 克隆即用,无 submodule 的复杂性。
- 仍可与原仓库同步更新。
⚠️ 注意:
--squash
是可选的,用于减少历史噪音。
✅ 方案二:直接复制代码(最简单,但失去同步能力)
如果子项目不常更新,或者您只是想“借用”一段代码,可以直接:
# 1. 克隆子项目到临时目录
git clone https://github.com/you/shared-utils.git /tmp/utils# 2. 复制代码到父项目
cp -r /tmp/utils/src ./libs/shared-utils/# 3. 手动添加并提交
git add libs/shared-utils/
git commit -m "feat: integrate shared-utils code directly"
✅ 优点:简单直接,代码完全属于父项目。
❌ 缺点:后续无法自动同步子项目的更新,需手动再复制。
❌ 为什么 git submodule
不能满足您?
因为 git submodule
的设计就是:
- 子项目是一个独立的 Git 仓库(有自己的
.git
)。 - 父项目只记录一个 Git 链接(gitlink),指向子项目的某个 commit。
- 所以父项目的提交中永远不会包含子项目的实际代码文件。
📌 换句话说:您不能用
submodule
实现“一起提交代码”,这是它的设计决定的。
✅ 总结:如何选择?
需求 | 推荐方案 |
---|---|
想要代码融合,提交时包含子项目代码,但仍能更新 | ✅ git subtree (最佳选择) |
只用一次,后续不更新 | ✅ 直接复制代码 |
需要独立版本控制,精确锁定 commit | ✅ git submodule (但您不想要这个) |
🔁 推荐操作(使用 git subtree
)
# 1. 添加子项目代码(融合)
git subtree add --prefix=libs/utils https://github.com/you/shared-utils.git main --squash# 2. 提交后,代码就在父仓库中了
git commit # 此时代码已包含# 3. 后续更新
git subtree pull --prefix=libs/utils https://github.com/you/shared-utils.git main --squash
这样,您就实现了“父工程提交时,子工程代码也一起提交”的目标。