前言
在开源项目的维护过程中,你是否遇到过这样的困扰:文档更新频繁触发主项目的 CI/CD 流程?文档部署配置与代码构建配置相互干扰?文档版本与代码版本难以同步?
本文将分享一个优雅的解决方案:使用 Git Submodule 将文档独立为单独仓库,实现文档的独立部署和版本管理,同时保持与主项目的关联。
问题背景
当前架构的问题
在 AutoScan 项目的早期,文档直接放在主项目仓库中:
autoscan-spring-boot-starter/
├── docs/
│ └── zh/
│ ├── index.html
│ ├── version.js
│ └── *.md
├── src/
├── pom.xml
└── README.md
这种架构带来了一系列问题:
问题 1:CI/CD 流程干扰
# 主项目的 GitHub Actions
on:
push:
paths:
- 'src/**'
- 'pom.xml'
# 文档更新也会触发构建 ❌
每次更新文档都会触发主项目的构建流程,浪费 CI/CD 资源。
问题 2:部署配置冲突
主项目需要:
- Maven 构建
- 单元测试
- 发布到 Maven Central
文档需要:
- GitHub Pages 部署
- 自定义域名
- HTTPS 配置
两种完全不同的部署需求在同一个仓库中配置,容易产生冲突。
问题 3:版本管理困难
主项目版本:v1.1.0
文档版本:v1.1.0
文档更新后:v1.1.0 (文档已更新,但代码未变)
文档和代码耦合在同一仓库,版本对应关系不清晰。
解决方案演进
方案 1:文档在主仓库内(当前方案)
autoscan-spring-boot-starter/
├── docs/
│ └── zh/
├── src/
└── pom.xml
优点:
- ✅ 文档和代码版本同步
- ✅ 简单直接,无需额外管理
缺点:
- ❌ 文档更新会触发主项目的 CI/CD
- ❌ 文档和代码耦合
- ❌ GitHub Pages 部署需要特殊配置
方案 2:文档单独仓库(不使用 Submodule)
autoscan-spring-boot-starter/ (主项目)
├── src/
└── pom.xml
autoscan-docs/ (独立仓库)
├── zh/
└── index.html
优点:
- ✅ 完全独立,最简单
- ✅ 各自独立部署
缺点:
- ❌ 文档和代码完全分离
- ❌ 版本同步困难
- ❌ 无法追溯特定版本的文档
方案 3:文档单独仓库 + Git Submodule(推荐方案)
autoscan-spring-boot-starter/
├── docs/ (submodule -> autoscan-docs)
├── src/
└── pom.xml
autoscan-docs/ (独立仓库)
├── zh/
│ ├── index.html
│ ├── version.js
│ └── *.md
└── README.md
优点:
- ✅ 文档独立部署和更新
- ✅ 主项目不受文档更新影响
- ✅ 可以独立配置文档的 CI/CD
- ✅ GitHub Pages / Cloudflare Pages 可以直接监听文档仓库
- ✅ 保持文档和代码的关联
- ✅ 可以追溯特定版本的文档
缺点:
- ⚠️ 需要管理 submodule
- ⚠️ 操作复杂度略增
推荐方案详解
架构设计
┌─────────────────────────────────────────┐
│ autoscan-spring-boot-starter │
│ (主项目仓库) │
│ │
│ ┌─────────────────────────────────┐ │
│ │ docs/ (submodule) │ │
│ │ └─> 指向 autoscan-docs 仓库 │ │
│ └─────────────────────────────────┘ │
│ │
│ src/ │
│ pom.xml │
│ README.md │
└─────────────────────────────────────────┘
│
│ submodule 引用
▼
┌─────────────────────────────────────────┐
│ autoscan-docs │
│ (文档仓库) │
│ │
│ zh/ │
│ ├── index.html │
│ ├── version.js │
│ └── *.md │
│ │
│ .github/workflows/ │
│ ├── update-doc-version.yml │
│ └── deploy.yml │
└─────────────────────────────────────────┘
核心优势
1. 独立部署
文档仓库可以独立部署到 GitHub Pages 或 Cloudflare Pages:
# autoscan-docs/.github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./
2. CI/CD 独立
文档更新不会触发主项目的构建:
# 主项目的 GitHub Actions
on:
push:
paths:
- 'src/**'
- 'pom.xml'
# 不包含 docs/ 目录
3. 版本关联
通过 submodule 保持文档和代码的版本对应:
# 主项目 v1.1.0 引用文档仓库的某个 commit
git submodule add https://github.com/itrys/autoscan-docs.git docs
# 发布 v1.2.0 时,更新 submodule 引用
git submodule update --remote
具体实施步骤
步骤 1:创建文档仓库
# 创建新的文档仓库
mkdir autoscan-docs
cd autoscan-docs
git init
# 创建目录结构
mkdir -p zh/js
# 创建文件
touch zh/index.html
touch zh/version.js
touch zh/_sidebar.md
touch zh/_coverpage.md
# 提交
git add .
git commit -m "init: 初始化文档仓库"
# 推送到远程
git remote add origin https://github.com/itrys/autoscan-docs.git
git push -u origin main
步骤 2:迁移文档内容
# 从主项目复制文档文件
cp -r ../autoscan-spring-boot-starter/docs/zh/* zh/
# 提交
git add .
git commit -m "docs: 迁移文档内容"
git push
步骤 3:在主项目中添加 Submodule
# 进入主项目目录
cd ../autoscan-spring-boot-starter
# 删除原来的 docs 目录
rm -rf docs
# 添加 submodule
git submodule add https://github.com/itrys/autoscan-docs.git docs
# 提交
git add .
git commit -m "chore: 使用 submodule 引用文档仓库"
git push
步骤 4:配置文档仓库的 GitHub Actions
创建 .github/workflows/update-doc-version.yml:
name: Update Doc Version
on:
push:
paths:
- 'zh/*.md'
branches:
- main
jobs:
update-version:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Update timestamp
run: |
TIMESTAMP=$(date +%Y%m%d%H%M)
sed -i "s/window.DOC_TIMESTAMP = '.*'/window.DOC_TIMESTAMP = '$TIMESTAMP'/" zh/version.js
echo "Updated timestamp to: $TIMESTAMP"
- name: Commit changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add zh/version.js
git commit -m "chore: auto-update doc timestamp [skip ci]" || echo "No changes to commit"
git push
创建 .github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
步骤 5:更新主项目的 README
在主项目的 README.md 中添加文档链接:
## 📚 文档
- [在线文档](https://itrys.github.io/autoscan-docs/)
- [快速开始](https://itrys.github.io/autoscan-docs/#/quickstart)
- [功能特性](https://itrys.github.io/autoscan-docs/#/features)
文档源码:[autoscan-docs](https://github.com/itrys/autoscan-docs)
Git Submodule 常用操作
克隆包含 submodule 的项目
# 方法1:递归克隆(推荐)
git clone --recursive https://github.com/itrys/autoscan-spring-boot-starter.git
# 方法2:先克隆再初始化
git clone https://github.com/itrys/autoscan-spring-boot-starter.git
cd autoscan-spring-boot-starter
git submodule init
git submodule update
# 方法3:在已克隆的项目中初始化 submodule
git submodule update --init --recursive
更新 submodule
# 更新 submodule 到最新版本
git submodule update --remote
# 更新特定 submodule
git submodule update --remote docs
# 在 submodule 目录中操作
cd docs
git pull origin main
cd ..
git add docs
git commit -m "docs: 更新文档"
查看 submodule 状态
# 查看 submodule 状态
git submodule status
# 查看 submodule 详细信息
git submodule
# 查看 submodule 的远程仓库
cd docs
git remote -v
删除 submodule
# 删除 submodule
git submodule deinit docs
git rm docs
rm -rf .git/modules/docs
git commit -m "chore: 移除文档 submodule"
版本同步策略
发布新版本时的流程
# 1. 更新主项目代码
vim src/main/java/...
# 2. 更新文档
cd docs
# 修改文档内容
vim zh/quickstart.md
# 更新版本号
vim zh/version.js
# 修改:window.DOC_VERSION = '1.2.0';
git add .
git commit -m "docs: 更新 v1.2.0 文档"
git push
# 3. 回到主项目,更新 submodule 引用
cd ..
git add docs
git commit -m "release: v1.2.0"
git push
版本对应关系
主项目 Commit → 文档仓库 Commit
v1.1.0 (abc123) → docs v1.1.0 (def456)
v1.2.0 (ghi789) → docs v1.2.0 (jkl012)
通过 submodule,可以精确追溯每个版本对应的文档内容。
最佳实践
1. 使用 Tag 标记版本
在文档仓库中打标签:
# 在文档仓库
cd autoscan-docs
git tag -a v1.1.0 -m "文档 v1.1.0"
git push origin v1.1.0
# 在主项目中引用特定标签
cd autoscan-spring-boot-starter
cd docs
git checkout v1.1.0
cd ..
git add docs
git commit -m "docs: 引用文档 v1.1.0"
2. 使用分支管理版本
# 为文档创建版本分支
cd autoscan-docs
git checkout -b v1.1.x
git push origin v1.1.x
# 主项目引用分支
cd autoscan-spring-boot-starter
git config -f .gitmodules submodule.docs.branch v1.1.x
git submodule update --remote
3. 自动化版本同步
创建 GitHub Actions 自动更新 submodule 引用:
# 主项目的 .github/workflows/update-docs.yml
name: Update Docs Submodule
on:
repository_dispatch:
types: [docs-updated]
jobs:
update-submodule:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Update submodule
run: |
git submodule update --remote
- name: Commit changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add docs
git commit -m "docs: 更新文档引用" || echo "No changes"
git push
4. 文档仓库的 README
在文档仓库中添加 README,说明文档与主项目的关系:
# AutoScan 文档
本仓库是 [AutoScan Spring Boot Starter](https://github.com/itrys/autoscan-spring-boot-starter) 的文档仓库。
## 文档版本
| 版本 | 主项目版本 | 更新内容 |
|------|-----------|----------|
| v1.1.0 | v1.1.0 | 新增通配符、排除扫描、自定义注解功能 |
| v1.0.0 | v1.0.0 | 初始版本 |
## 本地预览
```bash
# 克隆仓库
git clone https://github.com/itrys/autoscan-docs.git
cd autoscan-docs
# 启动本地服务器
python -m http.server 8000
# 或使用 docsify-server
npm install -g docsify-cli
docsify serve
贡献指南
请参考 主项目贡献指南。
## 常见问题
### Q1: Submodule 更新后,团队成员如何同步?
```bash
# 团队成员拉取最新代码时
git pull
git submodule update --init --recursive
# 或者使用一条命令
git pull --recurse-submodules
Q2: 如何避免 Submodule 的常见错误?
# 配置 Git 自动更新 submodule
git config --global submodule.recurse true
# 配置 Git 在状态检查时包含 submodule
git config --global status.submoduleSummary true
Q3: 如何在 CI/CD 中正确处理 Submodule?
# GitHub Actions
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true # 自动初始化和更新 submodule
方案对比总结
| 方案 | 独立部署 | 版本关联 | CI/CD 独立 | 维护成本 | 推荐度 |
|---|---|---|---|---|---|
| 文档在主仓库 | ❌ | ✅ | ❌ | 低 | ⭐⭐ |
| 文档单独仓库 | ✅ | ❌ | ✅ | 低 | ⭐⭐⭐ |
| Submodule | ✅ | ✅ | ✅ | 中 | ⭐⭐⭐⭐⭐ |
总结
通过 Git Submodule 将文档独立为单独仓库,我们实现了:
核心优势
- 独立部署 - 文档可以独立部署到 GitHub Pages / Cloudflare Pages
- 版本关联 - 通过 submodule 保持文档和代码的版本对应
- CI/CD 独立 - 文档更新不会触发主项目构建
- 灵活管理 - 可以独立配置文档的自动化流程
- 版本追溯 - 可以精确追溯每个版本对应的文档内容
适用场景
- ✅ 中大型开源项目
- ✅ 文档更新频繁的项目
- ✅ 需要独立部署文档的项目
- ✅ 多版本文档管理需求
这个方案完美解决了文档与代码耦合的问题,既保持了独立性,又维持了关联性,是开源项目文档管理的最佳实践之一。
参考资源
如果这篇文章对你有帮助,欢迎点赞、收藏、关注! 🎉