Contributing¶
git-city is an early, pre-release project, and contributions are welcome — bug reports, regression tests, and code for the parts of the roadmap that are not built yet.
This page covers how to set up a development environment, the quality gates every change must pass, the coding conventions the codebase follows, and where the design notes live.
git-city is built and managed with uv. You will need Python 3.12 or newer and Git installed.
Development setup¶
Clone the repository and sync the environment. uv sync creates a virtual environment and installs both the runtime and development dependencies from the lockfile.
Run the CLI from the working tree with uv run:
Both forms are equivalent — once git-city is installed on your PATH, Git also exposes it as a subcommand, so git city <cmd> works too. The docs use git city throughout.
No manual activation needed
You do not have to activate the virtual environment yourself. Every uv run <command> resolves and runs inside the project environment, so the quality gates below all start with uv run.
Quality gates¶
Every change must pass the full suite of checks below before it is merged. They are fast and run locally with uv run.
| Gate | Command | What it enforces |
|---|---|---|
| Tests | uv run pytest |
The full test suite: unit, integration, and end-to-end. |
| Lint | uv run ruff check |
Lint rules (style, bugs, dead code). |
| Format | uv run ruff format --check |
Code is formatted to the canonical style. |
| Types (ty) | uv run ty check src |
Type checking with ty. |
| Types (mypy) | uv run mypy src |
Type checking with mypy. |
| Types (pyrefly) | uv run pyrefly check |
Type checking with pyrefly. |
Run them in sequence before opening a pull request:
uv run pytest
uv run ruff check
uv run ruff format --check
uv run ty check src
uv run mypy src
uv run pyrefly check
Three type checkers, on purpose
git-city is checked with ty, mypy, and pyrefly. They disagree on edge cases, and passing all three keeps the type annotations honest and portable. If only one complains, the fix is usually a clearer annotation rather than a suppression.
Fixing format failures
ruff format --check only reports problems; run uv run ruff format to rewrite the files, and uv run ruff check --fix to apply safe lint fixes automatically.
For details on how the tests are organised — pure planner tables, interpreter tests against throwaway repos, and undo round-trips — see the testing guide.
→ Testing
Coding conventions¶
The codebase follows a strict functional core / imperative shell architecture, and contributions are expected to respect that boundary.
- Functional core.
read_repo_statebuilds an immutableRepoStatesnapshot — it is the only place that reads from Git. Pure planners then map(RepoState, intent)to aPlan, a list of small frozen-dataclass steps. Planners take data in and return data out: they never shell out to Git, never mutate, and never touch the filesystem. - Imperative shell. The interpreter is the only layer that runs Git commands and changes the world. New behavior generally means a new planner (pure, table-testable) plus, at most, a new step in the interpreter.
- Type hints everywhere. All code is fully annotated and must pass ty, mypy, and pyrefly. Prefer immutable, frozen dataclasses for the data that flows through the core.
- Small, focused functions. Keep functions short and single-purpose. A function that both reads state and mutates it is a sign the core/shell boundary has been crossed.
Keep the core pure
The single biggest rule: if a function in the planning layer reaches for Git, the filesystem, or mutable global state, it belongs in the shell instead. The purity of the core is what makes --dry-run, undo, and conflict pause/resume all fall out of one model — breaking it breaks all three at once.
For the full picture of how RepoState, planners, steps, and the interpreter fit together, read the architecture overview.
Design notes¶
The reasoning behind the design lives in the notes/ directory, alongside the source. Read these before proposing a significant change — they explain not just what the code does, but why it is shaped the way it is.
| File | Contents |
|---|---|
notes/01-vision.md |
The vision: what git-city is, the two-branch-type mental model, and how it differs from git-town. |
notes/02-execution-engine.md |
The execution engine: RepoState, pure planners, steps, the interpreter, and how dry-run / undo / pause all derive from one model. |
These notes pair with the internals pages here in the docs:
- The mental model and how the design choices follow from it → The mental model
- How the engine turns intent into reversible Git operations → Execution engine
- How the whole thing is tested → Testing