1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 05:37:52 +03:00

EB of git flow

This commit is contained in:
Anton Afanasyeu
2026-05-23 12:36:31 +02:00
parent a5ee5d7140
commit aa05e8e39c
19 changed files with 340 additions and 177 deletions

View File

@@ -1,12 +1,16 @@
#!/usr/bin/env python3
"""Build docs/GIT_FLOW.pdf from structured content and Mermaid diagrams.
Source of truth for edits: docs/GIT_FLOW.md (narrative) + this script (PDF layout).
Pipeline: Mermaid (.mmd) → SVG → high-DPI PNG (rsvg-convert) → PDF with correct aspect ratio.
Vector sources: docs/_pdf_build/git_flow_diagram_*.svg (open in Okular for crisp zoom).
Requires: reportlab, npx (@mermaid-js/mermaid-cli), rsvg-convert (librsvg).
Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py
"""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
@@ -16,6 +20,7 @@ from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.platypus import (
Image,
PageBreak,
@@ -30,35 +35,38 @@ ROOT = Path(__file__).resolve().parents[2]
BUILD = Path(__file__).resolve().parent
OUT_PDF = ROOT / "docs" / "GIT_FLOW.pdf"
# (mmd, svg, png, caption, mermaid w/h px, max width cm in PDF)
DIAGRAMS = [
("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.png", "Branch model (green master)"),
("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.png", "Daily development"),
("git_flow_diagram_release.mmd", "git_flow_diagram_release.png", "Release: next → master"),
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.png", "Hotfix decision and flow"),
("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.svg", "git_flow_diagram_branches.png",
"Branch model (green master)", 1600, 720, 15.5),
("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.svg", "git_flow_diagram_feature.png",
"Daily development", 1100, 1300, 14.0),
("git_flow_diagram_release.mmd", "git_flow_diagram_release.svg", "git_flow_diagram_release.png",
"Release: next → master", 1100, 1300, 14.0),
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.svg", "git_flow_diagram_hotfix.png",
"Hotfix decision and flow", 1200, 1000, 14.5),
]
MAX_DIAGRAM_HEIGHT_CM = 16.5
RSVG_DPI = 200
DOC_VERSION = "1.1"
DOC_DATE = "2026-05-21"
DOC_AUTHOR = "Anton Afanaasyeu"
DOC_SOURCE = "docs/GIT_FLOW.md"
def render_mermaid() -> None:
for mmd, png, _ in DIAGRAMS:
for mmd, svg_name, _, _, w_px, h_px, _ in DIAGRAMS:
src = BUILD / mmd
dst = BUILD / png
dst = BUILD / svg_name
if not src.exists():
raise FileNotFoundError(src)
subprocess.run(
[
"npx",
"--yes",
"@mermaid-js/mermaid-cli@11.4.0",
"-i",
str(src),
"-o",
str(dst),
"-b",
"white",
"-w",
"1200",
"-H",
"800",
"npx", "--yes", "@mermaid-js/mermaid-cli@11.4.0",
"-i", str(src), "-o", str(dst),
"-b", "white", "-w", str(w_px), "-H", str(h_px),
],
cwd=str(ROOT),
check=True,
@@ -66,6 +74,31 @@ def render_mermaid() -> None:
)
def svg_to_png(svg_path: Path, png_path: Path) -> None:
if not shutil.which("rsvg-convert"):
raise RuntimeError("rsvg-convert not found (install librsvg / gnome-base/rsvg)")
subprocess.run(
[
"rsvg-convert",
"-d", str(RSVG_DPI),
"-p", str(RSVG_DPI),
"-o", str(png_path),
str(svg_path),
],
check=True,
timeout=60,
)
def diagram_size_cm(png_path: Path, max_width_cm: float) -> tuple[float, float]:
"""Width/height in cm preserving aspect ratio, capped by page frame."""
w_px, h_px = ImageReader(str(png_path)).getSize()
max_w = max_width_cm * cm
max_h = MAX_DIAGRAM_HEIGHT_CM * cm
scale = min(max_w / w_px, max_h / h_px)
return w_px * scale, h_px * scale
def p(text: str, style) -> Paragraph:
return Paragraph(text.replace("\n", "<br/>"), style)
@@ -93,21 +126,20 @@ def add_table(story, headers: list[str], rows: list[list[str]], col_widths) -> N
story.append(t)
def add_diagram(story, mmd_png: str, caption: str, h2, iw: float = 16 * cm) -> None:
path = BUILD / mmd_png
if path.exists():
story.append(p(caption, h2))
story.append(Image(str(path), width=iw, height=iw * 0.55))
story.append(Spacer(1, 0.35 * cm))
def add_diagram(story, png_name: str, caption: str, h2, max_width_cm: float) -> None:
path = BUILD / png_name
if not path.exists():
return
iw, ih = diagram_size_cm(path, max_width_cm)
story.append(p(caption, h2))
story.append(Image(str(path), width=iw, height=ih))
story.append(Spacer(1, 0.35 * cm))
def build_pdf() -> None:
styles = getSampleStyleSheet()
title = ParagraphStyle(
"DocTitle",
parent=styles["Title"],
fontSize=18,
spaceAfter=10,
"DocTitle", parent=styles["Title"], fontSize=18, spaceAfter=10,
textColor=colors.HexColor("#1565C0"),
)
h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6)
@@ -118,16 +150,31 @@ def build_pdf() -> None:
story: list = []
story.append(p("Android Cast — Git flow (green master)", title))
story.append(Spacer(1, 0.15 * cm))
add_table(
story,
["Field", "Value"],
[
["Version", DOC_VERSION],
["Published", DOC_DATE],
["Author(s)", DOC_AUTHOR],
["Markdown source", DOC_SOURCE],
["Diagrams (PDF)", f"Mermaid → SVG → PNG @ {RSVG_DPI} DPI"],
["Diagrams (vector)", "docs/_pdf_build/git_flow_diagram_*.svg"],
],
[4.0 * cm, 12.5 * cm],
)
story.append(Spacer(1, 0.25 * cm))
story.append(
p(
"Development cycles, branch roles, release and hotfix strategy. "
"Version 1.0 · 2026-05-21. Markdown source: docs/GIT_FLOW.md",
"PDF diagrams are high-resolution raster with correct aspect ratio (not stretched). "
"For infinite zoom, open the SVG files in Okular or a browser. "
"Okular: View → Zoom for PDF; SVG opens as native vector.",
small,
)
),
)
story.append(Spacer(1, 0.3 * cm))
# 1 Goals
story.append(p("1. Goals", h1))
add_table(
story,
@@ -142,7 +189,6 @@ def build_pdf() -> None:
)
story.append(Spacer(1, 0.4 * cm))
# 2 Branches
story.append(p("2. Branches", h1))
add_table(
story,
@@ -156,31 +202,37 @@ def build_pdf() -> None:
[3 * cm, 8.5 * cm, 4 * cm],
)
story.append(Spacer(1, 0.3 * cm))
add_diagram(story, DIAGRAMS[0][1], DIAGRAMS[0][2], h2)
add_diagram(story, DIAGRAMS[0][2], DIAGRAMS[0][3], h2, DIAGRAMS[0][6])
story.append(PageBreak())
# 3 Daily dev
story.append(p("3. Daily development", h1))
for line in [
"1. git fetch && git checkout next && git pull",
"2. git checkout -b feature/short-description",
"3. Commit on topic branch; run tests before merge",
"4. Merge topic → next (after CI green)",
"5. Delete topic branch",
"git fetch && git checkout next && git pull",
"git checkout -b feature/short-description",
"Commit on topic branch; run tests before merge",
"Merge topic → next (after CI green)",
]:
story.append(p(f"{line}", bullet))
story.append(Spacer(1, 0.2 * cm))
story.append(Spacer(1, 0.15 * cm))
story.append(
p(
"<b>Topic branch cleanup:</b> optional locally. On the server, enable "
"<i>delete branch on merge</i> (Gitea/GitLab/GitHub) so merged feature/* "
"branches are removed automatically on origin.",
body,
),
)
story.append(Spacer(1, 0.15 * cm))
story.append(
p(
"<b>Agents:</b> never push directly to master or next unless the user explicitly "
"requests a release or hotfix merge.",
body,
)
),
)
add_diagram(story, DIAGRAMS[1][1], DIAGRAMS[1][2], h2, iw=15 * cm)
add_diagram(story, DIAGRAMS[1][2], DIAGRAMS[1][3], h2, DIAGRAMS[1][6])
story.append(PageBreak())
# 4 Release
story.append(p("4. Release (next → master)", h1))
for line in [
"Confirm next is QA-ready and CI green",
@@ -191,10 +243,9 @@ def build_pdf() -> None:
"Continue feature branches from updated next",
]:
story.append(p(f"{line}", bullet))
add_diagram(story, DIAGRAMS[2][1], DIAGRAMS[2][2], h2, iw=15 * cm)
add_diagram(story, DIAGRAMS[2][2], DIAGRAMS[2][3], h2, DIAGRAMS[2][6])
story.append(PageBreak())
# 5 Hotfix
story.append(p("5. Hotfixes — recommended strategy", h1))
story.append(
p(
@@ -202,7 +253,7 @@ def build_pdf() -> None:
"released code. After fix and tests: merge → master, tag patch (e.g. v0.1.3), then "
"<b>merge master → next</b> so integration does not miss the fix.",
body,
)
),
)
story.append(Spacer(1, 0.15 * cm))
story.append(
@@ -210,7 +261,7 @@ def build_pdf() -> None:
"<b>Exception (option 2):</b> bug exists only on next — branch fix/* from next, "
"merge to next only; ships with the next normal release.",
body,
)
),
)
story.append(Spacer(1, 0.15 * cm))
story.append(
@@ -218,9 +269,9 @@ def build_pdf() -> None:
"<b>Why not hotfix only on next?</b> next often contains unreleased features; "
"merging next to master for a patch would ship extra code or block the fix.",
body,
)
),
)
add_diagram(story, DIAGRAMS[3][1], DIAGRAMS[3][2], h2, iw=14 * cm)
add_diagram(story, DIAGRAMS[3][2], DIAGRAMS[3][3], h2, DIAGRAMS[3][6])
story.append(p("5.1 Sync cheat sheet", h2))
add_table(
@@ -236,7 +287,6 @@ def build_pdf() -> None:
)
story.append(PageBreak())
# 6 Protection + agents
story.append(p("6. Branch protection and CI", h1))
add_table(
story,
@@ -245,10 +295,21 @@ def build_pdf() -> None:
["CI required before merge", "yes", "yes"],
["No WIP direct push", "yes", "yes"],
["Annotated release tags", "yes", "no"],
["Force-push", "forbidden", "forbidden"],
["Force-push", "never", "maintainer only, rare"],
["Delete branch on merge", "n/a", "yes (topic branches)"],
],
[5.5 * cm, 5 * cm, 5 * cm],
)
story.append(Spacer(1, 0.2 * cm))
story.append(
p(
"<b>Force-push on next:</b> keep disabled by default. Allow only when you intentionally "
"rewrite integration history (e.g. squash fixups before release) — never on master. "
"Prefer merge/rebase on topic branches instead. If you force-push next, coordinate with "
"anyone else using the repo and re-sync their clones.",
body,
),
)
story.append(Spacer(1, 0.35 * cm))
story.append(p("7. Cursor / agent checklist", h1))
for line in [
@@ -256,17 +317,15 @@ def build_pdf() -> None:
"Run tests before recommending merge to next",
"Releases and tags only with explicit user approval",
"Hotfix released code from master; sync master into next after tag",
"Never force-push master or next",
"Never force-push master; avoid force-push on next unless user requests",
]:
story.append(p(f"{line}", bullet))
story.append(Spacer(1, 0.4 * cm))
story.append(
p(
"Full narrative and Mermaid sources: docs/GIT_FLOW.md, "
"docs/_pdf_build/git_flow_diagram_*.mmd · "
"Regenerate PDF: python3 docs/_pdf_build/build_git_flow_pdf.py",
"Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py",
small,
)
),
)
doc = SimpleDocTemplate(
@@ -284,12 +343,17 @@ def build_pdf() -> None:
def main() -> int:
try:
render_mermaid()
for _mmd, svg_name, png_name, *_ in DIAGRAMS:
svg_to_png(BUILD / svg_name, BUILD / png_name)
except subprocess.CalledProcessError as e:
print("Mermaid render failed:", e, file=sys.stderr)
print("Diagram render failed:", e, file=sys.stderr)
return 1
except FileNotFoundError as e:
print(e, file=sys.stderr)
return 1
except RuntimeError as e:
print(e, file=sys.stderr)
return 1
build_pdf()
return 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -11,5 +11,4 @@ sequenceDiagram
Dev->>CI: run tests
CI-->>Dev: pass
Dev->>Next: merge topic branch
Note over Next: next stays integration line
Dev->>Topic: delete branch
Note over Next: server may delete merged branch on origin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 149 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,3 @@
reportlab>=4.0
# PDF diagrams: Mermaid CLI (npx) + rsvg-convert (system package librsvg).