AI 开发中的 Git Submodule 父子仓库模式:前后端分仓管理与协作实践
当一个项目同时包含前端、后端、接口文档和启动脚本时,团队通常会在两种方案之间选择:
- 把所有代码放进一个 Monorepo;
- 保留前后端独立仓库,再用一个父仓库统一组织。
Git Submodule 属于第二种方案。它既能保持前后端仓库独立,又能让父仓库记录一组经过验证的版本组合。
进入 AI 辅助开发阶段后,这种模式又多了一层价值:父仓库可以成为 AI 的任务编排入口,子仓库则是边界清晰的执行单元。
不过,Submodule 也会带来初始化、分支、提交顺序和版本指针等额外复杂度。如果没有明确规则,AI 很容易改错仓库、漏推子仓库,或者只提交一个无法被其他人拉取的父仓库指针。
本文将从项目结构、日常开发、AI 协作和仓库治理四个角度,整理一套可落地的父子仓库管理模式。
!summary 核心结论
- 父仓库是项目的编排层和版本清单,不应堆放前后端业务代码。
- 前端、后端子仓库是独立 Git 仓库,分别维护代码、分支、测试和发布。
- AI 应从父仓库理解全局,但只在任务授权的子仓库中写代码。
- 跨端需求应先确定接口契约,再分别实现前后端,最后更新父仓库指针。
- 推送顺序必须是:先推子仓库,再推父仓库。
一、父子仓库模式解决什么问题?
假设一个项目由三个部分组成:
text
product-workspace/
├── web/ # 前端仓库
├── server/ # 后端仓库
└── docs/ # 父仓库维护的项目文档
团队希望实现下面这些目标:
- 前端和后端可以独立开发、发版和控制权限;
- 项目仍然有一个统一入口,方便初始化和联调;
- 父仓库能够准确记录"哪一个前端版本对应哪一个后端版本";
- AI 可以在一个工作区内理解完整业务,但不能随意跨越前后端边界;
- 新成员克隆项目后,可以快速获得一套可运行的前后端组合。
Git Submodule 的核心能力正好适合这个场景:
父仓库不保存子仓库的全部代码,而是记录子仓库路径、远程地址,以及当前应使用的 commit。
因此,父仓库本质上更像一份可执行的版本清单。
二、整体架构与职责划分
1. 父仓库负责什么?
父仓库适合维护:
.gitmodules;- 前后端子仓库的 commit 指针;
- 项目总览和架构说明;
- API 契约、联调约定和环境说明;
- 一键初始化、启动、检查脚本;
- 面向 AI 的全局规则,例如根目录
AGENTS.md; - 一组已经联调验证过的前后端版本组合。
父仓库不适合直接放置:
- 前端页面和组件业务代码;
- 后端 Controller、Domain、Repository 等业务实现;
- 只服务于单个子仓库的测试和构建配置。
2. 前端子仓库负责什么?
前端仓库独立维护:
- 页面、组件和路由;
- 状态管理;
- API Client 和类型定义;
- 前端单元测试、组件测试和 E2E;
- 前端构建与发布流程;
- 前端专属的
AGENTS.md或项目规则。
3. 后端子仓库负责什么?
后端仓库独立维护:
- API、Controller 或 BFF;
- Domain、Service、Repository;
- 数据库迁移;
- 权限和鉴权逻辑;
- 后端测试、构建和部署流程;
- 后端专属的 AI 开发规则。
4. 父仓库记录的不是"最新版本"
这是理解 Submodule 最重要的一点:
text
父仓库记录的是子仓库的一个确定 commit,
而不是自动跟随子仓库远程分支的最新代码。
即使 .gitmodules 中配置了:
ini
branch = main
普通的 git submodule update 仍然会检出父仓库记录的 commit。只有显式执行 git submodule update --remote,才会尝试按照配置分支向前更新,并让父仓库出现新的指针变化。
三、推荐的目录结构
text
product-workspace/
├── .gitmodules
├── AGENTS.md
├── README.md
├── docs/
│ ├── architecture.md
│ ├── api-contract.md
│ └── integration-checklist.md
├── scripts/
│ ├── bootstrap.sh
│ ├── dev.sh
│ └── status.sh
├── web/ # Git Submodule
│ ├── AGENTS.md
│ ├── package.json
│ └── src/
└── server/ # Git Submodule
├── AGENTS.md
├── pom.xml
└── src/
对应的 .gitmodules 可以是:
ini
[submodule "web"]
path = web
url = git@example.com:team/product-web.git
branch = main
[submodule "server"]
path = server
url = git@example.com:team/product-server.git
branch = main
!tip 地址选择 团队成员认证方式一致时,建议统一使用 SSH 或 HTTPS,不要在
.gitmodules中混用个人账号、Token 或带凭证的 URL。
四、初始化与状态识别
1. 推荐的克隆方式
第一次获取项目时,直接递归克隆:
bash
git clone --recurse-submodules <父仓库地址>
如果已经普通克隆了父仓库,再补充执行:
bash
git submodule update --init --recursive
这条命令会:
- 读取
.gitmodules; - 初始化尚未注册的子模块;
- 下载子仓库;
- 检出父仓库记录的 commit;
- 递归初始化更深层的子模块。
2. 为什么子目录是空的?
普通执行:
bash
git clone <父仓库地址>
只会获取父仓库。此时 web 和 server 可能只是空的占位目录,并不代表代码丢失。
先检查:
bash
git submodule status
常见状态前缀:
| 前缀 | 含义 |
|---|---|
- |
子模块尚未初始化 |
+ |
当前子模块 commit 与父仓库记录不一致 |
| 空格 | 子模块与父仓库记录一致 |
3. 一次查看父子仓库状态
bash
git status --short --branch
git submodule status --recursive
git -C web status --short --branch
git -C server status --short --branch
也可以批量检查:
bash
git submodule foreach --recursive 'git status --short --branch'
这组命令非常适合放进 AI 开发任务的第一步。AI 在改代码前,应该先确认:
- 父仓库当前分支;
- 两个子仓库当前分支;
- 是否处于 detached HEAD;
- 是否存在用户未提交的改动;
- 子模块指针是否已经偏离父仓库记录。
五、AI 开发中的仓库边界
传统开发中,人通常知道自己正在修改哪个仓库;AI 却容易把整个目录树视为一个项目。
如果没有明确规则,可能出现这些问题:
- 前端需求顺手修改后端实现;
- 后端字段变化后直接在前端硬编码兼容逻辑;
- 在父仓库提交,却遗漏子仓库里的真实代码;
- 在 detached HEAD 上产生无法追踪的 commit;
- 将多个仓库的用户已有改动一起覆盖或提交;
- 子仓库 commit 没有推送,父仓库却已经更新指针。
因此,AI 开发应该采用"全局理解、局部写入"的原则。
1. 全局理解
AI 从父仓库读取:
- 项目由哪些仓库组成;
- 前后端如何通信;
- 启动和测试命令;
- 接口契约在哪里;
- 哪些修改属于跨仓库变更;
- 提交和推送顺序。
2. 局部写入
根据任务类型限制写入范围:
| 任务类型 | 默认允许修改 | 默认禁止修改 |
|---|---|---|
| 前端页面或交互 | web/ |
server/ |
| 后端接口内部实现 | server/ |
web/ |
| API 字段调整 | 契约文档、server/、web/ |
无关模块 |
| 联调脚本和版本组合 | 父仓库 docs/、scripts/、子模块指针 |
子仓库业务代码 |
!warning 不要让 AI 自行扩大范围 如果需求原本只要求前端适配,AI 不应因为"后端改起来更方便"就修改后端。跨仓库变更必须在计划中明确列出影响和验证方式。
六、用 AGENTS.md 告诉 AI 如何工作
父仓库根目录可以放置一份全局 AGENTS.md,描述仓库地图和协作规则。
示例:
md
# 项目仓库说明
本项目由一个父仓库和两个 Git Submodule 组成:
- `web/`:前端仓库,负责页面、组件、状态和 API Client。
- `server/`:后端仓库,负责 API、业务领域、数据访问和数据库迁移。
- 父仓库:只负责文档、脚本和子模块版本组合。
## 修改边界
- 前端需求默认只修改 `web/`。
- 后端需求默认只修改 `server/`。
- 涉及接口字段时,先更新接口契约,再分别修改后端和前端。
- 不覆盖其他仓库中的用户已有改动。
- 不自动部署,不主动提交或推送。
## 开始任务前
1. 检查父仓库和两个子仓库的分支与工作区状态。
2. 确认是否存在 detached HEAD。
3. 明确本次任务需要修改哪些仓库。
4. 跨仓库修改必须列出实现顺序和验证计划。
## 验证
- 前端运行类型检查、相关测试和构建。
- 后端运行相关测试和构建。
- 跨端需求完成后执行联调验证。
## 提交顺序
先提交并推送子仓库,再提交父仓库中的子模块指针。
然后在 web/AGENTS.md 和 server/AGENTS.md 中分别补充技术栈、架构边界、测试命令和目录约定。
这种分层有两个好处:
- 父仓库规则负责全局协作;
- 子仓库规则负责具体技术实现。
七、分支管理策略
1. 避免直接在 detached HEAD 上开发
执行 git submodule update 后,子模块通常会处于 detached HEAD,因为它检出的是一个确定 commit,而不是本地分支。
开始开发前应进入子仓库并切换分支:
bash
git -C web switch main
git -C server switch main
开发新功能时,推荐父仓库和相关子仓库使用同名分支:
bash
git switch -c feat/user-profile
git -C web switch -c feat/user-profile
git -C server switch -c feat/user-profile
同名分支不是 Git Submodule 的强制要求,但能明显降低沟通和联调成本。
2. 不是所有任务都要创建三个分支
如果任务只涉及前端,可以:
text
父仓库:保持当前集成分支,或创建对应编排分支
web:创建 feat/user-profile
server:保持原有稳定分支,不产生修改
关键不是分支数量一致,而是父仓库最终记录的版本组合必须清晰、可解释。
3. AI 开始开发前的分支确认
建议让 AI 明确输出一张影响表:
| 仓库 | 当前分支 | 是否修改 | 目标分支 |
|---|---|---|---|
| 父仓库 | main |
是 | feat/user-profile |
web |
main |
是 | feat/user-profile |
server |
main |
是 | feat/user-profile |
这样可以在真正写代码前发现分支不一致、未提交改动和范围误判。
八、跨前后端需求的推荐执行顺序
以"新增用户资料字段"为例,推荐按下面的顺序处理。
第一步:定义接口契约
先明确:
- 请求和响应字段;
- 字段类型、是否必填和默认值;
- 错误码;
- 权限要求;
- 旧客户端兼容策略;
- 是否涉及数据库迁移。
契约可以放在父仓库 docs/api-contract.md,也可以来自 OpenAPI 等独立契约源。
第二步:实现后端
后端依次处理:
- 数据库和领域模型;
- Repository 或数据访问层;
- Domain、Service 或业务逻辑;
- Controller、BFF 或 API;
- 后端自动化测试;
- 接口文档或 Schema 更新。
第三步:实现前端
前端依次处理:
- API 类型和请求封装;
- 表单或展示组件;
- loading、empty、error 和 disabled 状态;
- 表单回填和提交参数;
- 组件测试或页面测试;
- 浏览器联调。
第四步:联调验证
至少确认:
- 前端请求字段与契约一致;
- 后端返回值与前端类型一致;
- 空值和错误分支可用;
- 旧数据可以正常展示;
- 前后端分别通过自己的测试和构建。
第五步:形成版本组合
前后端验证完成后,分别提交子仓库,再由父仓库记录两个新 commit。
text
接口契约
↓
后端实现与测试
↓
前端适配与测试
↓
联调验证
↓
推送 server
↓
推送 web
↓
更新并推送父仓库指针
九、正确的提交与推送顺序
子模块里的业务改动必须在子仓库中提交。
1. 提交后端
bash
git -C server status
git -C server add <本次相关文件>
git -C server commit -m "feat: 增加用户资料字段"
git -C server push -u origin feat/user-profile
2. 提交前端
bash
git -C web status
git -C web add <本次相关文件>
git -C web commit -m "feat: 支持编辑用户资料"
git -C web push -u origin feat/user-profile
3. 提交父仓库
子仓库成功推送后,再回到父仓库:
bash
git status
git diff --submodule=log
git add web server docs/api-contract.md
git commit -m "chore: 更新用户资料功能前后端版本"
git push -u origin feat/user-profile
父仓库中的 web 和 server 并不是普通文件变更,而是 Gitlink 指针变化。
!danger 为什么必须先推子仓库? 如果父仓库先推送,而子仓库的新 commit 只存在于你的本地,其他人拉取父仓库后会得到"找不到目标 commit"的错误。这类父仓库提交是不可复现的。
十、拉取更新的正确方式
1. 获取父仓库已经确认的版本组合
bash
git pull --ff-only
git submodule sync --recursive
git submodule update --init --recursive
这会让子仓库回到父仓库记录的 commit,适合部署、测试和复现环境。
2. 主动跟随子仓库远程分支
bash
git submodule update --remote --merge web server
这条命令会尝试把子模块更新到 .gitmodules 配置分支的远程版本,并让父仓库产生新的指针变化。
因此,它不应该作为所有场景下的无脑初始化命令。只有当你明确要"升级子模块版本"时才使用,并在升级后完成测试和父仓库提交。
十一、AI 任务提示词模板
1. 前端单仓任务
text
这是一个父仓库 + Git Submodule 项目。
本次任务只修改 web 前端子仓库,不修改 server 和父仓库业务配置。
开始前检查父仓库与 web 的分支和工作区状态,保留用户已有改动。
完成后运行前端相关测试、类型检查和构建,不提交、不推送、不部署。
2. 后端单仓任务
text
本次需求只修改 server 后端子仓库。
请保留现有 Controller、Domain、Repository 和 Adapter 边界。
不要为了配合实现而修改 web。
完成后运行相关后端测试与构建,并报告是否需要前端后续适配。
3. 跨前后端任务
text
这是一个跨 web 与 server 的功能需求。
请先检查父仓库和两个子仓库的状态,然后给出:
1. 接口契约变化;
2. 后端实现范围;
3. 前端适配范围;
4. 测试与联调计划;
5. 三个仓库的分支和提交顺序。
确认范围后再实现。分别验证前端和后端,最后检查父仓库的 Submodule 指针变化。
不要自动提交、推送或部署。
十二、常见问题与事故模式
| 问题 | 原因 | 处理方式 |
|---|---|---|
web、server 是空目录 |
子模块未初始化 | git submodule update --init --recursive |
| 子仓库显示 detached HEAD | 按父仓库记录的 commit 检出 | 开发前切换或创建本地分支 |
父仓库显示 modified content |
子仓库存在未提交文件 | 进入对应子仓库检查 git status |
父仓库显示 new commits |
子仓库 HEAD 与记录指针不同 | 确认是否需要提交新的指针 |
| 其他人无法拉取子模块 commit | 子仓库 commit 没有推送 | 先推子仓库,再推父仓库 |
| AI 只提交了父仓库 | 误把 Submodule 当普通目录 | 分别在子仓库提交真实代码 |
| 拉取父仓库后子仓库没更新 | 只执行了 git pull |
再执行 git submodule update --init --recursive |
branch = main 但代码没到最新 |
父仓库仍固定 commit | 需要时显式执行 update --remote 并提交指针 |
| 前后端分支对不上 | 多仓库分支没有统一管理 | 开始任务前输出仓库影响表 |
| 切换父仓库分支后代码混乱 | 子模块指针没有同步 | 执行 git submodule update --init --recursive |
十三、什么时候不适合使用 Submodule?
Submodule 并不是前后端拆分的唯一答案。
下面这些场景可能更适合 Monorepo:
- 前后端几乎每个需求都必须原子提交;
- 共享类型和公共包修改非常频繁;
- CI 必须对全部模块做统一依赖分析;
- 团队很难接受多仓库分支和提交管理;
- 发布流程要求一个 commit 同时表示全部业务代码变化;
- AI 经常需要执行大规模、跨前后端的同步重构。
下面这些场景更适合 Submodule:
- 前后端仓库需要独立权限和生命周期;
- 两端大多数需求可以独立交付;
- 父仓库主要承担客户交付、集成测试或环境编排;
- 一个后端需要服务多个前端;
- 团队需要长期保留经过验证的版本组合;
- 不同子仓库有独立 CI、发布节奏和负责人。
选择的关键不是"哪个方案更先进",而是跨仓库变更频率、团队边界和发布模型。
十四、团队落地检查清单
初始化
- 父仓库存在正确的
.gitmodules - 新成员使用
--recurse-submodules克隆 - 启动脚本会检查子模块是否初始化
- 子模块 URL 不包含个人凭证
开发
- 开始任务前检查父子仓库分支和工作区
- 不在 detached HEAD 上直接开发
- 明确本次任务影响哪些仓库
- 跨端需求先更新接口契约
- 前后端分别运行自己的测试和构建
AI 协作
- 父仓库有全局
AGENTS.md - 子仓库有各自的技术栈和验证规则
- AI 默认只修改任务授权的仓库
- AI 不覆盖其他仓库中的用户已有改动
- AI 不自动部署或扩大修改范围
提交与交付
- 子仓库代码分别提交
- 子仓库 commit 已成功推送
- 父仓库只记录可访问的子模块 commit
- 使用
git diff --submodule=log检查版本变化 - 最终版本组合已经完成前后端联调
总结
Git Submodule 父子仓库模式的价值,不只是"把多个仓库放进一个目录"。
它真正建立的是三层关系:
- 父仓库负责组织和版本组合;
- 子仓库负责独立业务实现和交付;
- 接口契约负责连接前后端边界。
在 AI 开发中,父仓库还可以承担第四个角色:为 AI 提供全局上下文、任务边界和协作规则。
只要团队始终遵守下面四条原则,Submodule 就能保持稳定:
text
先检查仓库状态
先明确修改边界
先推子仓库
最后更新父仓库指针
反过来,如果团队长期忽略 detached HEAD、分支对应关系和子模块指针,Submodule 就会从版本治理工具变成额外负担。
因此,是否采用父子仓库模式并不取决于命令是否复杂,而取决于团队能否把仓库边界、AI 规则和交付顺序固化成稳定流程。