PlantUML 在 git 工程里的全生命周期:pre-commit / CI / git-lfs / PR 评审
PlantUML 图源 + 渲染产物都是 git 仓库的一部分。这一篇整理 end-to-end 实践:改 puml → 校验 → 自动渲 → commit → CI → PR 评审 → rebase → 一切顺。
PlantUML 在 git 仓库的两类「文件」
1 2 3 4 5 6 7
| project/ ├── docs/diagrams/ ← source │ ├── auth.puml │ └── system.puml └── public/img/diagrams/ ← rendered output ├── auth.svg └── system.svg
|
两个目录的工作流:
- puml 改动 → 触发 render → SVG 改动 → 一起 commit
- 只改 SVG → 审计告警:「应该改 puml 而不是 SVG」
- 只改 puml → CI 失败:「图有渲染失败」
理想状态:
任何 commit 里,puml 和 SVG 要么都改,要么都不改。
pre-commit hook 强制一致性
用 husky + lint-staged
1 2
| npm install --save-dev husky lint-staged npx husky install
|
.husky/pre-commit:
1 2 3 4 5 6 7 8 9 10 11
| #!/bin/sh
git diff --cached --name-only --diff-filter=ACMR -- '*.puml' | \ xargs -I{} plantuml -checkonly {}
for f in $(git diff --cached --name-only -- '*.puml'); do echo "→ checking $f" plantuml -checkonly "$f" || exit 1 done
|
.lintstagedrc.json:
1 2 3 4 5
| { "*.puml": [ "plantuml -checkonly" ] }
|
检查图已被 commit(强制 puml+SVG 一并提交)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #!/bin/sh
changed_puml=$(git diff --cached --name-only --diff-filter=ACMR -- '*.puml' | sed 's|^docs/diagrams/||;s|\.puml$||') changed_svg=$(git diff --cached --name-only --diff-filter=ACMR -- '*.svg' | sed 's|^public/img/diagrams/||;s|\.svg$||')
missing="" for p in $changed_puml; do if ! echo "$changed_svg" | grep -q "^$p$"; then missing="$missing $p.svg" fi done
if [ -n "$missing" ]; then echo "❌ 修改了 puml 但 SVG 没更新: $missing" echo " 先跑 plantuml 渲染" exit 1 fi
|
实际使用:
1 2 3 4 5 6 7 8 9 10 11 12
| git add docs/diagrams/auth.puml git commit -m "改 auth 架构"
npx plantuml -tsvg docs/diagrams/auth.puml -o public/img/diagrams/ git add public/img/diagrams/auth.svg git commit -m "改 auth 架构"
|
CI 渲染脚本
一次渲染全部图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| name: Render PlantUML diagrams on: push: paths: ['docs/diagrams/**'] pull_request: paths: ['docs/diagrams/**']
jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install PlantUML run: | sudo apt-get update sudo apt-get install -y fonts-noto-cjk graphviz default-jdk-headless wget -O /tmp/plantuml.jar https://github.com/plantuml/plantuml/releases/download/v1.2026.x/plantuml-1.2026.x.jar - name: Validate syntax run: | while IFS= read -r f; do java -jar /tmp/plantuml.jar -checkonly "$f" done < <(find docs/diagrams -name '*.puml') - name: Render run: | java -jar /tmp/plantuml.jar -tsvg -thread 4 -o public/img/diagrams docs/diagrams/*.puml - name: Check for unauthorized SVG edits run: | git diff --exit-code public/img/diagrams/ || { echo "❌ SVG 改动但 puml 没动" exit 1 } - name: Commit rendered run: | git config user.name "github-actions[bot]" git config user.email "actions@github.com" git add public/img/diagrams/ git commit -m "ci: re-render diagrams" || echo "无渲染变化" git push
|
关键:git diff --exit-code 让「图渲染产物变更但 puml 没动」成为 build fail。
错误恢复
CI 渲染失败(比如某种图类型忘了装支持库):
1 2
| plantuml -tsvg docs/diagrams/*.puml
|
解决:
- 装对应库:
sudo apt-get install graphviz
- 客户端 / 服务端都验证:本地 CLI + 在线 / TeaVM 客户端
- 重 push
git-lfs 跟踪大图
何时用 git-lfs
- 单张 SVG > 50 KB(CJK 图常见)
- PNG > 500 KB
- 图库多到仓库总大小 > 50 MB
- 不想 git diff / git clone 拖慢
配置 .gitattributes
1 2 3
| # .gitattributes *.svg filter=lfs diff=svg merge=union *.png filter=lfs diff=png merge=union
|
初次:
1 2 3
| git lfs install git lfs track "public/img/diagrams/*.svg" git add .gitattributes
|
迁移历史(大图没经过 lfs 时):
1
| git lfs migrate import --include="*.svg"
|
坑:lfs 文件 checkout 时需要 git-lfs 二进制—— CI runner 必须装:
1 2 3 4 5 6
| - name: Setup git-lfs run: | sudo apt-get install -y git-lfs git lfs install git lfs fetch git lfs checkout
|
PR 评审看图变更
GitHub 原生 SVG diff
GitHub 自动渲染 SVG diff——但实际上:
- SVG 默认被当二进制 diff,不显示内容变化
- 开启方式:在
.gitattributes 标 *.svg diff=svg
这样 PR 会显示 diff 文本(清晰看到节点位置、配色变化)。
pre-rendered raster diff
更可控:CI 跑一个「渲染并生成 diff image」步骤:
1 2 3 4 5 6 7 8
| git checkout main plantuml -tsvg docs/diagrams/auth.puml -o /tmp/before/ git checkout feature-branch plantuml -tsvg docs/diagrams/auth.puml -o /tmp/after/
npx lookatme compare /tmp/before/auth.svg /tmp/after/auth.svg --output diff.png
|
PR 评论里贴 diff.png 让人直观看到「图怎么变了」。
文本 diff 更轻量
puml 本身是文本——直接 git diff 看 source 变更:
1 2 3
| git diff docs/diagrams/auth.puml
|
PR reviewer 一眼看出。SVG 是渲染产物,rebase / merge 时频繁冲突——源仓存 puml,render cache 由 CI 维护。
rebase / merge 的图同步问题
rebase 时冲突
1 2 3 4
| .feature-branch └── docs/diagrams/auth.puml (修改了 SVG output) .main └── public/img/diagrams/auth.svg (其他 commit 改过时也改了 SVG)
|
rebase 时 git 看到两边都对 SVG 改了,冲突。
解决方案 1:源仓只存 puml
1 2 3 4
| .gitignore: public/img/diagrams/
# CI 重新渲染所有图
|
rebase 后:
1 2 3
| plantuml -tsvg docs/diagrams/*.puml -o public/img/diagrams/ git add public/img/diagrams/ git rebase --continue
|
解决方案 2:git pull 时自动重新渲染
1 2 3 4
| .gitattributes: *.svg merge=union
# Git 默认 union merge 策略对 SVG 取并集
|
但 union merge 不能解决「两边内容冲突」(图拓扑变了),只能对「图片叠合变化」有效。对 PlantUML SVG 来说 union merge 经常生成混合——不推荐。
解决方案 3:CI 强制 SVG 一致
1 2 3 4 5 6
| CI: 1. 检出代码 2. 清空 public/img/diagrams/ 3. plantuml 重新渲染所有 4. git diff --exit-code 5. 如果 diff 不为空:自动 commit 一个 "sync svg" commit
|
这种是最干净的方案——SVG 完全由 CI 维护,不入 puml-dev 流程。
一组完整的配置样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
set -e
cat >> .gitignore <<EOF
# PlantUML render output - regenerated by CI public/img/diagrams/ EOF
cat > .gitattributes <<EOF # PlantUML SVG: treat as text for diff *.svg diff=svg merge=ours EOF git add .gitattributes
mkdir -p .husky cat > .husky/pre-commit <<'EOF'
set -e git diff --cached --name-only --diff-filter=ACMR -- '*.puml' | \ while IFS= read -r f; do echo "→ checking $f" plantuml -checkonly "$f" || { echo "❌ $f syntax error" exit 1 } done EOF chmod +x .husky/pre-commit
mkdir -p .github/workflows cat > .github/workflows/render.yml <<'EOF' name: render on: push: paths: ['docs/diagrams/**'] jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install -y plantuml - run: plantuml -tsvg -thread 4 -o out/ docs/diagrams/*.puml - run: | git diff --exit-code out/ || { echo "图渲染了还没 commit" exit 1 } EOF
echo "✓ PlantUML git workflow 已配置" echo "" echo "下一步:" echo " pre-commit hook 装了,但需要 install" echo " $ npx husky install"
|
单作者 vs 多作者工作流
单作者
- 直接
git add . && git commit
- pre-commit 校验 + 本地渲染
- push
多作者团队
- 中央仓库(main + feature 分支)
- feature 分支可能改图——不要直接 push main
- PR review 看到文本 diff + CI 看到渲染 diff
- merge main 时 rebase + 重新渲染
Fork 派
- fork 改 puml + 渲染 → PR upstream
- upstream 看不到 SVG 的「本地版」(fork 私有的)
- main 收 PR → 立即重渲 → 上公开仓库
关键检查(季度 audit)
1 2 3 4 5 6 7 8
| [ ] .gitignore 阻挡 SVG 输出 [ ] .gitattributes 标记 SVG diff [ ] pre-commit 校验 puml [ ] CI 强制渲染一致 [ ] git-lfs 跟踪大图(如需要) [ ] PR template 要求看图 diff [ ] merge main 后自动重新渲染 [ ] husky 装上了(git hooks 路径)
|
落地 puml.online 自身的实践
我们的项目:
source/_posts/plantuml-*.md 是 source
public/blog/plantuml-*/index.html 是渲染产物
- ✅ 不在 git 跟踪
public/ —— 在 .gitignore 里
- ✅ CI 在
npm run build 后 hexo re-generate
- ❌ 没有 pre-commit(我们用单独 review)
- ❌ 没有 git-lfs(hexo 输出小)
实践:source of truth = markdown(内嵌 puml)。渲染产物不入 git。
小结
- PlantUML 在 git 的关键是:source(puml/md)和 output(svg)分离—— source 入 git,output 由 CI 重渲
- pre-commit hook 让作者「改 puml 必须连带渲 SVG」
- git-lfs 解决大图仓库体积
- rebase / merge 时强制 re-render 是最干净的姿势
下一步