PlantUML git lifecycle — pre-commit / CI / git-lfs / PR review

puml.online

PlantUML source + rendered output both live in a git repo. This post distils the end-to-end practice: edit puml → validate → auto-render → commit → CI → PR review → rebase → all smooth.

Two kinds of “files” PlantUML uses in 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

Workflow for these two directories:

  • puml edited → triggers render → SVG edited → commit together.
  • only SVG edited → audit alarm: “should have edited puml not SVG.”
  • only puml edited → CI failure: “diagram rendering failed.”

The ideal state:

In any commit, puml and SVG are both changed or both unchanged.

pre-commit hook enforces consistency

Using 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
# .husky/pre-commit
# 1. Validate all staged .puml
git diff --cached --name-only --diff-filter=ACMR -- '*.puml' | \
xargs -I{} plantuml -checkonly {}

# 2. Validate puml edits will produce SVG updates (don't render, just validate source)
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"
]
}

Check that the diagram has been committed (force puml+SVG together)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh
# .husky/pre-commit — force puml+svg to be committed together
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 edited but SVG not updated: $missing"
echo " Run plantuml render first"
exit 1
fi

Real usage:

1
2
3
4
5
6
7
8
9
10
11
12
git add docs/diagrams/auth.puml
git commit -m "modify auth architecture"
# pre-commit phase
# → detects auth.puml changed
# → public/img/diagrams/auth.svg not changed
# → refuses to commit

# Recovery
npx plantuml -tsvg docs/diagrams/auth.puml -o public/img/diagrams/
git add public/img/diagrams/auth.svg
git commit -m "modify auth architecture"
# → pass

CI rendering script

Render everything in one pass

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
# .github/workflows/render-diagrams.yml
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 changed but puml not changed"
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 "No rendering changes"
git push

Key: git diff --exit-code makes “diagram output changed but puml not” a build failure.

Error recovery

CI rendering failure (some diagram type’s support library missing):

1
2
plantuml -tsvg docs/diagrams/*.puml
# ❌ ERROR: Diagram not supported

Fix:

  1. Install the lib: sudo apt-get install graphviz
  2. Validate on both client and server: local CLI + online / TeaVM client
  3. Push again

git-lfs for large diagrams

When to use git-lfs

  • Single SVG > 50 KB (CJK diagrams commonly)
  • PNG > 500 KB
  • Total image library > 50 MB
  • Want to avoid git diff / git clone slowdown

Configure .gitattributes

1
2
3
# .gitattributes
*.svg filter=lfs diff=svg merge=union
*.png filter=lfs diff=png merge=union

First time:

1
2
3
git lfs install
git lfs track "public/img/diagrams/*.svg"
git add .gitattributes

Migrate history (large images not under lfs yet):

1
git lfs migrate import --include="*.svg"

Pitfall: lfs files need git-lfs binary at checkout — CI runner must install:

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 review with image diff

GitHub native SVG diff

GitHub auto-renders SVG diffs — but in reality:

  • SVG is treated as binary diff by default — content changes not shown.
  • Enable: in .gitattributes add *.svg diff=svg.

Then PR shows text diff (clearer node position, colour changes).

Pre-rendered raster diff

More controllable: CI runs a “render and generate diff image” step:

1
2
3
4
5
6
7
8
# 1. pre-merge state render
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/

# 2. use image-diff to generate a diff image
npx lookatme compare /tmp/before/auth.svg /tmp/after/auth.svg --output diff.png

Paste diff.png into the PR comment for reviewers to see “how the diagram changed” at a glance.

Text diff is lighter weight

puml itself is text — git diff for source changes:

1
2
3
git diff docs/diagrams/auth.puml
# - Alice -> Bob: hello
# + Alice -> Bob: change hello to hi

PR reviewers spot it instantly. SVG is render output — frequent conflicts in rebase / merge. Source repo holds puml; render cache maintained by CI.

rebase / merge diagram sync problem

Conflict during rebase

1
2
3
4
.feature-branch
└── docs/diagrams/auth.puml (modified SVG output too)
.main
└── public/img/diagrams/auth.svg (other commit also changed SVG)

git sees both branches changed the SVG, conflict.

Solution 1: source repo only stores puml

1
2
3
4
.gitignore:
public/img/diagrams/

# CI re-renders all diagrams

After rebase:

1
2
3
plantuml -tsvg docs/diagrams/*.puml -o public/img/diagrams/
git add public/img/diagrams/ # re-stage
git rebase --continue

Solution 2: auto re-render on git pull

1
2
3
4
.gitattributes:
*.svg merge=union

# Git's default union merge strategy unions SVG

But union merge can’t resolve “both branches changed content” (topology changed), only “image overlay changes” work. For PlantUML SVG, union merge often produces composite but invalid — not recommended.

Solution 3: CI enforces SVG consistency

1
2
3
4
5
6
CI:
1. Checkout code
2. Empty public/img/diagrams/
3. plantuml re-render everything
4. git diff --exit-code
5. If diff non-empty: auto-commit a "sync svg" commit

The cleanest solution — SVG fully maintained by CI, never in the puml-dev workflow.

A complete configuration sample

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
# scripts/setup-puml-git.sh
# Set up PlantUML workflow in a git project
set -e

# 1. .gitignore
cat >> .gitignore <<EOF

# PlantUML render output - regenerated by CI
public/img/diagrams/
EOF

# 2. .gitattributes
cat > .gitattributes <<EOF
# PlantUML SVG: treat as text for diff
*.svg diff=svg merge=ours
EOF
git add .gitattributes

# 3. pre-commit hook
mkdir -p .husky
cat > .husky/pre-commit <<'EOF'
#!/bin/sh
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

# 4. CI workflow
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 "diagrams rendered but not committed"
exit 1
}
EOF

# 5. Done
echo "✓ PlantUML git workflow configured"
echo ""
echo "Next steps:"
echo " pre-commit hook installed, but you need to install"
echo " $ npx husky install"

Single-author vs multi-author workflow

Single author

  • Just git add . && git commit
  • pre-commit validates + local renders
  • push

Multi-author team

  • Central repo (main + feature branches)
  • feature branches may modify diagrams — don’t push directly to main
  • PR review sees text diff + CI sees render diff
  • When merging to main: rebase + re-render

Fork-based

  • fork edits puml + renders → PR upstream
  • Upstream can’t see fork’s “local” SVG
  • main accepts PR → immediately re-render → publish public repo

Key checklist (quarterly audit)

1
2
3
4
5
6
7
8
[ ] .gitignore excludes SVG output
[ ] .gitattributes tags SVG for diff
[ ] pre-commit validates puml
[ ] CI forces render consistency
[ ] git-lfs for large images (if needed)
[ ] PR template requires image diff review
[ ] auto-re-render after merging main
[ ] husky installed (git hooks path)

puml.online’s own practice

Our project:

  • source/_posts/plantuml-*.md is source
  • public/blog/plantuml-*/index.html is rendered output
  • ✅ do NOT track public/ in git — in .gitignore
  • ✅ CI does hexo re-generate on npm run build
  • ❌ no pre-commit (we use review-only)
  • ❌ no git-lfs (hexo output small)

Practice: source of truth = markdown (containing puml). Rendered output never enters git.

Recap

  • PlantUML’s key in git: separate source (puml/md) and output (svg) — source in git, output re-rendered by CI.
  • pre-commit hook forces authors to “edit puml and re-render SVG together”.
  • git-lfs solves large-image repo size.
  • Force re-render during rebase / merge is the cleanest posture.

Next

  • Title: PlantUML git lifecycle — pre-commit / CI / git-lfs / PR review
  • Author: puml.online
  • Created at : 2026-07-30 13:16:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-git-lifecycle-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.