Skip to content

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.

git clone <repo-url> git-city
uv sync

Run the CLI from the working tree with uv run:

uv run git-city
uv run python -m git_city

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_state builds an immutable RepoState snapshot — it is the only place that reads from Git. Pure planners then map (RepoState, intent) to a Plan, 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.

Architecture


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