Metadata-Version: 2.4
Name: gitsvg
Version: 0.3.6
Summary: CLI that renders git tree visualizations as SVG from JSONL input.
Project-URL: Homepage, https://github.com/bertpl/gitsvg
Project-URL: Documentation, https://gitsvg.readthedocs.io/
Project-URL: Source, https://github.com/bertpl/gitsvg
Project-URL: Changelog, https://github.com/bertpl/gitsvg/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/bertpl/gitsvg/issues
Author: Bert Pluymers
License-Expression: MIT
License-File: LICENSE
Keywords: cli,diagram,git,svg,visualization
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Version Control :: Git
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: click!=8.3.0,!=8.3.1,>=8.2.0
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: drawsvg>=2.4.0
Requires-Dist: pydantic>=2.10.0; python_version < '3.14'
Requires-Dist: pydantic>=2.12.0; python_version >= '3.14'
Description-Content-Type: text/markdown

# gitsvg

CLI that renders git tree visualizations as SVG from JSONL input.

[![CI](https://img.shields.io/github/actions/workflow/status/bertpl/gitsvg/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/gitsvg/actions/workflows/push_to_main.yml)
[![Coverage](https://img.shields.io/badge/coverage-98.33%25-brightgreen)](https://github.com/bertpl/gitsvg/actions/workflows/push_to_main.yml)
[![Tests](https://img.shields.io/badge/tests-1186-blue)](https://github.com/bertpl/gitsvg/actions/workflows/push_to_main.yml)
[![PyPI](https://img.shields.io/pypi/v/gitsvg.svg)](https://pypi.org/project/gitsvg/)
[![Python](https://img.shields.io/pypi/pyversions/gitsvg.svg)](https://pypi.org/project/gitsvg/)
[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/bertpl/gitsvg/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-readthedocs-blue)](https://gitsvg.readthedocs.io/)
[![code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230)](https://github.com/astral-sh/ruff)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/bertpl/gitsvg/badge)](https://scorecard.dev/viewer/?uri=github.com/bertpl/gitsvg)

## Installation

```bash
pip install gitsvg
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv tool install gitsvg
```

Releases are published to PyPI via [trusted publishing](https://docs.pypi.org/trusted-publishers/) with [PEP 740](https://peps.python.org/pep-0740/) digital attestations.

## Quick start

A `.gitsvg.jsonl` file is a list of operations, one JSON object per line, applied top-to-bottom to build a diagram. Render it with:

```bash
gitsvg render diagram.gitsvg.jsonl -o diagram.svg
```

Validate without rendering:

```bash
gitsvg validate diagram.gitsvg.jsonl
```

## Diagrams

The [`examples/`](https://github.com/bertpl/gitsvg/tree/v0.3.6/examples) folder ships self-contained input files demonstrating the format. The first seven examples cover the diagram operations; the [Theming](#theming) section below covers visual customization. Each subsection shows the rendered output and the source it came from.

### Example 1: Linear history

A single branch with a few commits. The minimum viable diagram.

![Linear history](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/01_linear.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "c1", "msg": "initial commit", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c2", "msg": "add README", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c3", "msg": "add tests", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c4", "msg": "fix typo", "hash": "auto"}
```

### Example 2: Branch and merge

A `feature` branch forks off `main`, accumulates a couple of commits, then merges back.

![Branch and merge](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/02_branch_merge.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "c1", "msg": "initial commit", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c2", "msg": "setup config", "hash": "auto"}
{"op": "branch", "name": "feature", "from_branch": "main"}
{"op": "commit", "branch": "feature", "id": "f1", "msg": "add login form", "hash": "auto"}
{"op": "commit", "branch": "feature", "id": "f2", "msg": "wire up auth", "hash": "auto"}
{"op": "merge", "from": "feature", "into": "main", "as": "m1", "msg": "merge feature", "hash": "auto"}
```

### Example 3: Multiple branches with lane reuse

Two concurrent branches share lanes 1 and 2; after both merge, a later `feature-b` reclaims the now-free lane 1 instead of starting a new one. Lane assignment is automatic and geometry-driven.

![Multiple branches with lane reuse](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/03_multi_branch.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "c1", "msg": "initial commit", "hash": "auto"}
{"op": "branch", "name": "feature-a", "from_branch": "main"}
{"op": "commit", "branch": "feature-a", "id": "a1", "msg": "start feature A", "hash": "auto"}
{"op": "branch", "name": "bugfix", "from_branch": "main"}
{"op": "commit", "branch": "bugfix", "id": "x1", "msg": "fix #42", "hash": "auto"}
{"op": "merge", "from": "feature-a", "into": "main", "as": "m1", "msg": "merge feature A", "hash": "auto"}
{"op": "merge", "from": "bugfix", "into": "main", "as": "m2", "msg": "merge bugfix", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c2", "msg": "release prep", "hash": "auto"}
{"op": "branch", "name": "feature-b", "from_branch": "main"}
{"op": "commit", "branch": "feature-b", "id": "b1", "msg": "feature B", "hash": "auto"}
{"op": "merge", "from": "feature-b", "into": "main", "as": "m3", "msg": "merge feature B", "hash": "auto"}
```

### Example 4: Highlighting a commit

The `highlight` op marks an existing commit with an enlarged dot and a bold label — useful for drawing attention to a release or a key milestone.

![Highlighted release commit](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/04_highlight.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "c1", "msg": "initial commit", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c2", "msg": "feature work", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c3", "msg": "more feature work", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "v1", "msg": "release v1.0", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c4", "msg": "post-release fix", "hash": "auto"}
{"op": "highlight", "commit": "v1"}
```

### Example 5: Remove and rebuild (rebase pattern)

A `feature` branch is removed and re-declared on top of a more recent `main` commit, with the same commit IDs as before. This is the rebase-style "move my work onto the new tip" pattern, expressed as primitives.

![Remove and rebuild](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/05_remove_rebuild.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "c1", "msg": "initial commit", "hash": "auto"}
{"op": "branch", "name": "feature", "from_branch": "main"}
{"op": "commit", "branch": "feature", "id": "f1", "msg": "WIP feature", "hash": "auto"}
{"op": "commit", "branch": "feature", "id": "f2", "msg": "more WIP", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c2", "msg": "main moves on", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "c3", "msg": "more main work", "hash": "auto"}
{"op": "remove", "branches": ["feature"]}
{"op": "branch", "name": "feature", "from_branch": "main"}
{"op": "commit", "branch": "feature", "id": "f1", "msg": "WIP feature", "hash": "auto"}
{"op": "commit", "branch": "feature", "id": "f2", "msg": "more WIP", "hash": "auto"}
```

### Example 6: Import and squash

The `import` op replays another file as a prelude — here it picks up the rebased state from Example 5. A single new commit then squashes `f1` and `f2` into one via `replaces:`.

![Import and squash](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/06_import_squash.svg)

```jsonl
{"op": "import", "path": "05_remove_rebuild.gitsvg.jsonl"}
{"op": "commit", "branch": "feature", "replaces": ["f1", "f2"], "id": "f_squash", "msg": "complete feature", "hash": "auto"}
```

### Example 7: Open pull request

The `pull_request` op declares a pending merge between two branches. Both endpoints live-track the current branch tips: as commits accumulate on either side, the dashed arc advances. The optional `title` renders as a pill anchored at the source-tip commit. The typical lifecycle closes the PR with `remove` and then runs a real `merge`; this example stops before either, so the open PR remains visible in the final state.

![Open pull request](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/07_pull_request.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "m1", "msg": "release"}
{"op": "branch", "name": "feature", "from_branch": "main"}
{"op": "commit", "branch": "feature", "id": "f1", "msg": "wip"}
{"op": "pull_request", "id": "pr1", "from": "feature", "into": "main", "title": "PR 1: add thing"}
{"op": "commit", "branch": "feature", "id": "f2", "msg": "polish"}
{"op": "commit", "branch": "main", "id": "m2", "msg": "hotfix"}
```

## Theming

The `theme` op customizes the diagram's presentational surface — spacings, sizes, fonts, the branch-color palette, the SVG background, the orientation, and more. Each op only overrides the fields it lists; a `name` (`default`, `muted`, `dark`, `compact`) selects a built-in theme to base resolution on.

### Example 8: Recolored palette

Here we import Example 3 unchanged and apply a saturated palette with thicker strokes, larger labels, and a warm background.

![Recolored palette](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/08_themed.svg)

```jsonl
{"op": "import", "path": "03_multi_branch.gitsvg.jsonl"}
{"op": "theme", "background_color": "#fff8e7", "branch_spacing": 110, "branch_line_width": 4, "label_font_size": 14, "branch_label_font_size": 14, "hash_font_size": 11, "commit_radius": 7, "highlight_radius": 9, "branch_name_pill_offset_commit_axis_in_rows": -0.56, "colors": {"main": "#d62728", "branch1": "#1f77b4", "branch2": "#2ca02c", "branch3": "#ff7f0e", "branch4": "#9467bd"}}
```

### Example 9: Horizontal orientation

A `theme.orientation` of `lr` flips the diagram left-to-right: the commit axis grows rightward and branches stack downward. The same input renders identically in `bt` (default, bottom-to-top), `tb` (top-to-bottom), and `rl` (right-to-left); pill placement, margin defaults, and label-side mapping all adjust per orientation. Accepted values include the canonical short codes (`bt`, `tb`, `lr`, `rl`) and common aliases (Mermaid's `TD`, CSS's `ltr` / `rtl`, long forms like `bottom_to_top`, and `top_down` / `bottom_up`).

![Horizontal orientation](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/09_horizontal.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "m1", "msg": "init", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "m2", "msg": "release v1", "hash": "auto", "highlight": true}
{"op": "branch", "name": "feature", "from_branch": "main"}
{"op": "commit", "branch": "feature", "id": "f1", "msg": "wip", "hash": "auto"}
{"op": "commit", "branch": "feature", "id": "f2", "msg": "polish", "hash": "auto"}
{"op": "branch", "name": "docs", "from_branch": "main"}
{"op": "commit", "branch": "docs", "id": "d1", "msg": "readme", "hash": "auto"}
{"op": "merge", "into": "main", "from": "feature", "msg": "merge feature", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "m3", "msg": "hotfix", "hash": "auto"}
{"op": "pull_request", "id": "pr1", "from": "docs", "into": "main", "title": "PR 1: docs update"}
{"op": "theme", "orientation": "lr"}
```

### Example 11: Unique commit rows

By default (`commit_row_mode: shared`) commits on different branches may share a row, which keeps diagrams compact. Setting `theme.commit_row_mode` to `unique` gives every commit its own row, assigned in authoring order — so reading along the commit axis recovers the exact order events were declared, even when work on several branches interleaves. Useful for storytelling diagrams where the sequence of events is the point.

![Unique commit rows](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/11_unique_rows.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "m1", "msg": "project start", "hash": "auto"}
{"op": "branch", "name": "auth", "from_branch": "main"}
{"op": "commit", "branch": "auth", "id": "a1", "msg": "login form", "hash": "auto"}
{"op": "branch", "name": "ui", "from_branch": "main"}
{"op": "commit", "branch": "ui", "id": "u1", "msg": "navbar", "hash": "auto"}
{"op": "commit", "branch": "auth", "id": "a2", "msg": "oauth", "hash": "auto"}
{"op": "commit", "branch": "ui", "id": "u2", "msg": "dark mode", "hash": "auto"}
{"op": "merge", "from": "auth", "into": "main", "as": "m2", "msg": "merge auth", "hash": "auto"}
{"op": "commit", "branch": "ui", "id": "u3", "msg": "polish", "hash": "auto"}
{"op": "merge", "from": "ui", "into": "main", "as": "m3", "msg": "merge ui", "hash": "auto"}
{"op": "theme", "commit_row_mode": "unique"}
```

### Example 12: Auto lane change

By default a branch keeps its lane for its whole life, so once an inner branch merges its lane sits empty while outer branches stay stranded. Setting `theme.auto_lane_change` to `true` compacts the graph the way real git tools do: as each lower lane frees up, the surviving branches migrate inward, so the live branches always occupy the lowest lanes. The shift is drawn as a short connector in the branch line's own style — here `feat-b` slides into the lane `feat-a` vacates, and `feat-c` follows into `feat-b`'s.

![Auto lane change](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/12_auto_lane_change.svg)

```jsonl
{"op": "branch", "name": "main", "label_side": "before"}
{"op": "commit", "branch": "main", "id": "m1", "msg": "project start", "hash": "auto"}
{"op": "branch", "name": "feat-a", "from_branch": "main"}
{"op": "commit", "branch": "feat-a", "id": "a1", "msg": "feature a", "hash": "auto"}
{"op": "branch", "name": "feat-b", "from_branch": "main"}
{"op": "commit", "branch": "feat-b", "id": "b1", "msg": "feature b", "hash": "auto"}
{"op": "branch", "name": "feat-c", "from_branch": "main"}
{"op": "commit", "branch": "feat-c", "id": "c1", "msg": "feature c", "hash": "auto"}
{"op": "merge", "from": "feat-a", "into": "main", "as": "ma", "msg": "merge a", "hash": "auto"}
{"op": "commit", "branch": "feat-b", "id": "b2", "msg": "more b", "hash": "auto", "gap": 1}
{"op": "commit", "branch": "feat-c", "id": "c2", "msg": "more c", "hash": "auto"}
{"op": "merge", "from": "feat-b", "into": "main", "as": "mb", "msg": "merge b", "hash": "auto"}
{"op": "commit", "branch": "feat-c", "id": "c3", "msg": "polish c", "hash": "auto", "gap": 1}
{"op": "merge", "from": "feat-c", "into": "main", "as": "mc", "msg": "merge c", "hash": "auto"}
{"op": "commit", "branch": "main", "id": "m2", "msg": "release", "hash": "auto"}
{"op": "theme", "auto_lane_change": true}
```

## Named themes

Beyond `default`, gitsvg ships four built-in themes:

- **`muted`** — the pre-refresh default look: a softer, grayer branch palette with plain circle merge dots.
- **`dark`** — One Dark-inspired palette on a `#282c34` canvas.
- **`compact`** — ~30 % denser spacing with smaller fonts.
- **`gui`** — a desktop-git-GUI look: table layout (graph left, message and hash columns right, branch names as tip pills), auto lane change, tight spacing, and a system-color branch palette.

Selecting one is a single field on a `theme` op:

```jsonl
{"op": "import", "path": "03_multi_branch.gitsvg.jsonl"}
{"op": "theme", "name": "dark"}
```

The shipped preview below tiles the refreshed `default` next to the `muted`, `dark`, and `compact` themes on a shared input file, with the table-layout `gui` theme shown beneath on its own richer diagram:

![Built-in named themes](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/10_named_themes.svg)

The `gui` theme is best seen at full size. Its shipped example (`examples/13_gui_table.gitsvg.jsonl`) renders a multi-branch history the way a desktop git client would — the graph on the left, a per-commit table on the right, and each branch's name as a colored pill at the commit its ref points to:

![Desktop-GUI table theme](https://raw.githubusercontent.com/bertpl/gitsvg/v0.3.6/examples/13_gui_table.svg)

Selecting a named theme also wipes any `theme:` field overrides and `branch.color` overrides accumulated earlier — useful for "use exactly this theme." To layer a chosen theme on top of those overrides instead (e.g. when importing a diagram that already carries its own theming), pass `keep_prior_overrides: true` on the same op.

## CLI reference

| Command | Purpose |
|---------|---------|
| `gitsvg render <input> -o <output>` | Render a `.gitsvg.jsonl` file to SVG. Pass a directory at both ends to recursively walk the input tree and write mirrored `.svg` outputs under the output directory. `--small=N` selects minification level 0-3; bare `--small` is level 2 (lossless structural compression). |
| `gitsvg state <input>` | Emit a JSON snapshot of the diagram (branches, commits with their parent chain, open pull requests) — a structural description of the resolved graph. Stdout by default; pass `-o <file>` to write to a file, or pass a directory pair to recursively walk and emit one `<stem>.state.json` per input. Output format may change before 1.0; pin a gitsvg version when caching the schema. |
| `gitsvg layout <input>` | Emit a JSON view of the resolved layout (grid extent, lane assignments, commit positions, arcs, open pull-request geometry) — what the renderer consumes, useful for debugging visual placement. Same invocation matrix as `state` (stdout by default, `-o <file>` for a file, directory pair for recursive walking with `<stem>.layout.json` outputs). Output format may change before 1.0; pin a gitsvg version when caching the schema. |
| `gitsvg validate <input>` | Run the full validation pipeline; report errors with `file:line: [code] field: message`. Add `--json` for a structured report. |
| `gitsvg schema` | Index of all input operations. `gitsvg schema <op>` prints the JSON Schema for one operation (e.g. `gitsvg schema theme` for the theme op's field schema); `--list-ops` prints a bare op list. |
| `gitsvg theme` | Index of the built-in named themes with one-line descriptions. `gitsvg theme <name>` prints a named theme's resolved field values; `--list-names` prints a bare name list. |
| `gitsvg errors` | Index of all validation error codes. `gitsvg errors <code>` prints the long-form catalog entry; `--list-codes` prints a bare code list. |

`gitsvg schema` and `gitsvg errors` are designed for agents and tooling: an LLM-based agent producing input can fetch the schema for a single op and the catalog entry for any error it hits, without reading the rest of the documentation.

## Integrations

Keep diagram source under version control and render it where you need it:

- **[gitsvg-action](https://github.com/bertpl/gitsvg-action)** — a GitHub Action to render, validate, or drift-check `.gitsvg.jsonl` diagrams in CI (`uses: bertpl/gitsvg-action@v1`).
- **[mkdocs-gitsvg](https://github.com/bertpl/mkdocs-gitsvg)** — a MkDocs plugin that renders ` ```gitsvg ` fenced blocks to inline SVG at build time (in active development).

## License

[MIT](https://github.com/bertpl/gitsvg/blob/main/LICENSE).
