Data model: Git paths are data, not text

GitPath (value object, kernel.git.paths)

  • Fields: parts: tuple[str, ...] — repository-relative components, no empty, . or .. component.
  • Construction: GitPath.parse(text) strips one trailing / (git's directory marker) and splits on /; rejects absolute paths and ..; GitPath.parse("") is the root (parts == ()).
  • Relations (component-wise): ==, is_ancestor_of(other) (strict prefix), contains(other) (== or ancestor), overlaps(other) (either contains the other). Root overlaps nothing (an empty path never obstructs).
  • Rendering: str(p) / p.as_posix() → "/".join(parts); p.name.
  • Invariant: GitPath.parse(str(p)) == p.

StatusEntry (kernel.git.listing)

  • xy: str (two chars), path: GitPath, orig_path: GitPath | None (renames/copies only), is_directory: bool (git reported a trailing slash).
  • Derived: is_untracked (??), is_ignored (!!), is_conflicted (unmerged codes), index, worktree.
  • line — a display rendering XY path (with <- orig) for messages; never parsed back.

NameStatusEntry (kernel.git.listing)

  • status: str (e.g. M, A, D, R100), path: GitPath, orig_path: GitPath | None.

GitResult / GitCommandError (kernel.git.runner)

  • GitResult(returncode: int, stdout: bytes, stderr: bytes).
  • GitCommandError(RuntimeError): argv, cwd, returncode, stderr (decoded text); raised for a non-zero exit when check=True, or when git cannot be executed.
  • IndexEntry (kernel.git.listing): mode: str, oid: str, stage: int, path: GitPath, tag: str | None (the ls-files -v tag, filled only when tags=True).
  • NumstatEntry (kernel.git.listing): added: int | None, deleted: int | None (None for a binary file), path: GitPath.
  • TreeEntry (kernel.git.listing): mode: str, type: str (blob, tree, commit), oid: str, path: GitPath.
  • GitCommandError also carries timed_out: bool.