mirror of
git://f0xx.org/android_cast
synced 2026-07-29 07:39:15 +03:00
git flow, infra descriptions, cast flow changes (mobile side)
This commit is contained in:
298
docs/_pdf_build/build_git_flow_pdf.py
Normal file
298
docs/_pdf_build/build_git_flow_pdf.py
Normal file
@@ -0,0 +1,298 @@
|
||||
#!/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).
|
||||
Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from reportlab.lib import colors
|
||||
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.platypus import (
|
||||
Image,
|
||||
PageBreak,
|
||||
Paragraph,
|
||||
SimpleDocTemplate,
|
||||
Spacer,
|
||||
Table,
|
||||
TableStyle,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BUILD = Path(__file__).resolve().parent
|
||||
OUT_PDF = ROOT / "docs" / "GIT_FLOW.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"),
|
||||
]
|
||||
|
||||
|
||||
def render_mermaid() -> None:
|
||||
for mmd, png, _ in DIAGRAMS:
|
||||
src = BUILD / mmd
|
||||
dst = BUILD / png
|
||||
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",
|
||||
],
|
||||
cwd=str(ROOT),
|
||||
check=True,
|
||||
timeout=180,
|
||||
)
|
||||
|
||||
|
||||
def p(text: str, style) -> Paragraph:
|
||||
return Paragraph(text.replace("\n", "<br/>"), style)
|
||||
|
||||
|
||||
def add_table(story, headers: list[str], rows: list[list[str]], col_widths) -> None:
|
||||
data = [headers] + rows
|
||||
t = Table(data, colWidths=col_widths)
|
||||
t.setStyle(
|
||||
TableStyle(
|
||||
[
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")),
|
||||
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
||||
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
||||
("FONTSIZE", (0, 0), (-1, -1), 9),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey),
|
||||
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 4),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
|
||||
]
|
||||
)
|
||||
)
|
||||
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 build_pdf() -> None:
|
||||
styles = getSampleStyleSheet()
|
||||
title = ParagraphStyle(
|
||||
"DocTitle",
|
||||
parent=styles["Title"],
|
||||
fontSize=18,
|
||||
spaceAfter=10,
|
||||
textColor=colors.HexColor("#1565C0"),
|
||||
)
|
||||
h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6)
|
||||
h2 = ParagraphStyle("H2", parent=styles["Heading2"], fontSize=11, spaceBefore=8, spaceAfter=4)
|
||||
body = ParagraphStyle("Body", parent=styles["Normal"], fontSize=10, leading=14, alignment=TA_LEFT)
|
||||
small = ParagraphStyle("Small", parent=body, fontSize=9, textColor=colors.grey)
|
||||
bullet = ParagraphStyle("Bullet", parent=body, leftIndent=14, bulletIndent=6)
|
||||
|
||||
story: list = []
|
||||
story.append(p("Android Cast — Git flow (green master)", title))
|
||||
story.append(
|
||||
p(
|
||||
"Development cycles, branch roles, release and hotfix strategy. "
|
||||
"Version 1.0 · 2026-05-21. Markdown source: docs/GIT_FLOW.md",
|
||||
small,
|
||||
)
|
||||
)
|
||||
story.append(Spacer(1, 0.3 * cm))
|
||||
|
||||
# 1 Goals
|
||||
story.append(p("1. Goals", h1))
|
||||
add_table(
|
||||
story,
|
||||
["Goal", "Mechanism"],
|
||||
[
|
||||
["Always shippable master", "Merge to master only when CI/tests pass"],
|
||||
["Safe integration", "Features land on next first"],
|
||||
["Clear production line", "Release tags only on master"],
|
||||
["Fast production patches", "hotfix/* from master, then sync to next"],
|
||||
],
|
||||
[4.5 * cm, 12 * cm],
|
||||
)
|
||||
story.append(Spacer(1, 0.4 * cm))
|
||||
|
||||
# 2 Branches
|
||||
story.append(p("2. Branches", h1))
|
||||
add_table(
|
||||
story,
|
||||
["Branch", "Role", "Direct commits?"],
|
||||
[
|
||||
["master", "Production; green; annotated tags vX.Y.Z", "No — merge only"],
|
||||
["next", "Integration / release candidate", "No — merge only"],
|
||||
["feature/*, fix/*", "Short-lived work", "Yes; merge → next"],
|
||||
["hotfix/*", "Urgent fix for released code", "Yes; merge → master first"],
|
||||
],
|
||||
[3 * cm, 8.5 * cm, 4 * cm],
|
||||
)
|
||||
story.append(Spacer(1, 0.3 * cm))
|
||||
add_diagram(story, DIAGRAMS[0][1], DIAGRAMS[0][2], h2)
|
||||
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",
|
||||
]:
|
||||
story.append(p(f"• {line}", bullet))
|
||||
story.append(Spacer(1, 0.2 * 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)
|
||||
story.append(PageBreak())
|
||||
|
||||
# 4 Release
|
||||
story.append(p("4. Release (next → master)", h1))
|
||||
for line in [
|
||||
"Confirm next is QA-ready and CI green",
|
||||
"Merge next → master",
|
||||
"Smoke / CI on master",
|
||||
"Tag on master: git tag -a v0.1.2 -m \"Release v0.1.2\"",
|
||||
"Sync: git checkout next && git merge master && git push",
|
||||
"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)
|
||||
story.append(PageBreak())
|
||||
|
||||
# 5 Hotfix
|
||||
story.append(p("5. Hotfixes — recommended strategy", h1))
|
||||
story.append(
|
||||
p(
|
||||
"<b>Default (option 1):</b> branch hotfix/* from <b>master</b> when the bug affects "
|
||||
"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(
|
||||
p(
|
||||
"<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(
|
||||
p(
|
||||
"<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)
|
||||
|
||||
story.append(p("5.1 Sync cheat sheet", h2))
|
||||
add_table(
|
||||
story,
|
||||
["Event", "Action"],
|
||||
[
|
||||
["After release + tag", "merge master → next"],
|
||||
["After hotfix + tag", "merge master → next"],
|
||||
["Feature done", "merge topic → next only"],
|
||||
["Wrong base branch", "rebase topic onto latest next"],
|
||||
],
|
||||
[4.5 * cm, 12 * cm],
|
||||
)
|
||||
story.append(PageBreak())
|
||||
|
||||
# 6 Protection + agents
|
||||
story.append(p("6. Branch protection and CI", h1))
|
||||
add_table(
|
||||
story,
|
||||
["Rule", "master", "next"],
|
||||
[
|
||||
["CI required before merge", "yes", "yes"],
|
||||
["No WIP direct push", "yes", "yes"],
|
||||
["Annotated release tags", "yes", "no"],
|
||||
["Force-push", "forbidden", "forbidden"],
|
||||
],
|
||||
[5.5 * cm, 5 * cm, 5 * cm],
|
||||
)
|
||||
story.append(Spacer(1, 0.35 * cm))
|
||||
story.append(p("7. Cursor / agent checklist", h1))
|
||||
for line in [
|
||||
"Branch from next for features and non-production fixes",
|
||||
"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",
|
||||
]:
|
||||
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",
|
||||
small,
|
||||
)
|
||||
)
|
||||
|
||||
doc = SimpleDocTemplate(
|
||||
str(OUT_PDF),
|
||||
pagesize=A4,
|
||||
leftMargin=2 * cm,
|
||||
rightMargin=2 * cm,
|
||||
topMargin=2 * cm,
|
||||
bottomMargin=2 * cm,
|
||||
)
|
||||
doc.build(story)
|
||||
print(f"Wrote {OUT_PDF}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
render_mermaid()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Mermaid render failed:", e, file=sys.stderr)
|
||||
return 1
|
||||
except FileNotFoundError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
build_pdf()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
21
docs/_pdf_build/git_flow_diagram_branches.mmd
Normal file
21
docs/_pdf_build/git_flow_diagram_branches.mmd
Normal file
@@ -0,0 +1,21 @@
|
||||
flowchart TB
|
||||
subgraph legend[" "]
|
||||
direction LR
|
||||
L1[Long-lived]
|
||||
L2[Short-lived]
|
||||
end
|
||||
|
||||
M["master\n(always green, tagged releases)"]
|
||||
N["next\n(integration / release candidate)"]
|
||||
F["feature/* fix/*\n(fork next)"]
|
||||
H["hotfix/*\n(fork master)"]
|
||||
|
||||
F -->|"merge + CI green"| N
|
||||
N -->|"release: merge + CI green"| M
|
||||
H -->|"urgent patch + CI green"| M
|
||||
M -->|"sync after release or hotfix"| N
|
||||
|
||||
style M fill:#c8e6c9,stroke:#2e7d32
|
||||
style N fill:#bbdefb,stroke:#1565c0
|
||||
style F fill:#fff9c4,stroke:#f9a825
|
||||
style H fill:#ffccbc,stroke:#e64a19
|
||||
BIN
docs/_pdf_build/git_flow_diagram_branches.png
Normal file
BIN
docs/_pdf_build/git_flow_diagram_branches.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
15
docs/_pdf_build/git_flow_diagram_feature.mmd
Normal file
15
docs/_pdf_build/git_flow_diagram_feature.mmd
Normal file
@@ -0,0 +1,15 @@
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Dev as Developer or agent
|
||||
participant Topic as feature/* or fix/*
|
||||
participant Next as next
|
||||
participant CI as Tests / CI
|
||||
|
||||
Dev->>Next: fetch, checkout, pull
|
||||
Dev->>Topic: create branch from next
|
||||
Dev->>Topic: commits
|
||||
Dev->>CI: run tests
|
||||
CI-->>Dev: pass
|
||||
Dev->>Next: merge topic branch
|
||||
Note over Next: next stays integration line
|
||||
Dev->>Topic: delete branch
|
||||
BIN
docs/_pdf_build/git_flow_diagram_feature.png
Normal file
BIN
docs/_pdf_build/git_flow_diagram_feature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
21
docs/_pdf_build/git_flow_diagram_hotfix.mmd
Normal file
21
docs/_pdf_build/git_flow_diagram_hotfix.mmd
Normal file
@@ -0,0 +1,21 @@
|
||||
flowchart TD
|
||||
Start([Defect found])
|
||||
Q{Affects released code\non master / latest tag?}
|
||||
|
||||
Start --> Q
|
||||
Q -->|Yes| A[Branch hotfix/* from master]
|
||||
A --> B[Fix + tests]
|
||||
B --> C[Merge to master]
|
||||
C --> D[Tag patch on master e.g. v0.1.3]
|
||||
D --> E[Merge master into next]
|
||||
E --> End([Done])
|
||||
|
||||
Q -->|No, only on next| F[Branch fix/* from next]
|
||||
F --> G[Fix + tests]
|
||||
G --> H[Merge to next only]
|
||||
H --> End2([Ships with next release])
|
||||
|
||||
style C fill:#c8e6c9
|
||||
style D fill:#c8e6c9
|
||||
style E fill:#bbdefb
|
||||
style H fill:#bbdefb
|
||||
BIN
docs/_pdf_build/git_flow_diagram_hotfix.png
Normal file
BIN
docs/_pdf_build/git_flow_diagram_hotfix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
16
docs/_pdf_build/git_flow_diagram_release.mmd
Normal file
16
docs/_pdf_build/git_flow_diagram_release.mmd
Normal file
@@ -0,0 +1,16 @@
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Next as next
|
||||
participant Master as master
|
||||
participant CI as Tests / CI
|
||||
participant Tag as tag vX.Y.Z
|
||||
|
||||
Note over Next: feature complete, QA ok
|
||||
Next->>CI: full test suite
|
||||
CI-->>Next: green
|
||||
Next->>Master: merge next to master
|
||||
Master->>CI: smoke on master
|
||||
CI-->>Master: green
|
||||
Master->>Tag: annotated tag on master
|
||||
Master->>Next: merge master into next (sync)
|
||||
Note over Next: continue features from synced next
|
||||
BIN
docs/_pdf_build/git_flow_diagram_release.png
Normal file
BIN
docs/_pdf_build/git_flow_diagram_release.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user