Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# AGENTS.md

Guidance for future Codex sessions working in this repository.

## Project Shape

`mgit` is a small Python CLI for inspecting and operating on one git checkout
or a workspace containing many git checkouts. The CLI uses argparse subcommands,
short aliases, separation between command dispatch, git state modeling, repo discovery,
and output rendering.

Important files:

- `src/mgit/cli.py`: current Click entry point, option parsing, and clean flows.
- `src/mgit/git.py`: git command runner, URL parsing, branch/status/config
parsers, reports, fetch/pull/clone helpers, and cleanable-branch detection.
- `tests/`: current pytest coverage, prefer exercising CLI in general rather than unit tests.

## Development Commands

Use these from the repo root:

- `ruff check`, `ruff format --diff`, and `.venv/bin/pytest` are the normal
validation loop for changes. Run these before handing work back.
- `.venv/bin/pytest -q` is also useful for compact fast iteration.
- `.venv/bin/pytest -vv` when you want the full test names.
- `.venv/bin/pytest -vv --cov=src tests` for local coverage checks.
- `ruff check` and `ruff format --diff` directly; `ruff` is on `PATH`.

The following do similar work as above, just tox wrapped (no need to use these usually)
- `tox -e style` for the packaged style environment.
- `tox -e py39` for the minimum supported Python version.
- `tox -e py314` for the newest supported Python version.
- `tox` for occasional deeper confidence runs; the user and CI run it
periodically, so do not run it for every ordinary iteration unless asked.

`uv run pytest ...` also works when the synced environment is desired, but the
local `.venv/bin/pytest` path is usually faster while iterating.

The repo uses `ruff`, `pyright`, `tox`, and `setuptools-scm`.

## Working Rules

- Prefer `rg` and `rg --files` for navigation.
- Preserve user changes.
- Use `apply_patch` for manual edits.
- Keep changes scoped. This repo is intentionally small.
- Do not add new runtime dependencies without consulting with user.

## Safety Notes

Plain status inspection is safe. Some current v1 code paths are mutating:

- `fetch` runs `git fetch --all --prune`.
- `pull` runs `git pull --rebase`, guarded by pending-change checks.
- `groom` can delete one local and/or remote branch.

Do not run destructive clean/reset paths casually while planning or testing.
23 changes: 21 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ The short names are the intended interface:
- ``fetch`` / ``f``: refresh remote refs, then show what changed.
- ``pull`` / ``p``: pull with rebase only when pending work will not be disturbed.
- ``main`` / ``m``: checkout the default branch, even if it is ``master``.
- ``branches`` / ``b``: list local branches with useful small annotations.
- ``branches`` / ``b``: list local branches with presence and cleanup annotations.
- ``legend`` / ``l``: explain status and branch symbols.
- ``groom`` / ``g``: safely finish with the merged branch you are currently on.

The command shape is::
Expand Down Expand Up @@ -92,10 +93,28 @@ In one glance you get:

``✅`` means recently refreshed, ``☑️`` means the local picture may be older,
``⌛`` calls out notably stale fetch information, a branch-level ``🪦`` means
its upstream is gone, and ``👻`` means detached ``HEAD``. A bracket such as
the branch has been proven cleanable, and ``👻`` means detached ``HEAD``. A bracket such as
``[+2🪦+1]`` says that, besides the current and default branches, two local
branches have been proven cleanable and one remains.

The branch list spells that state out when you need to decide what is done::

mgit b
commands-instead-of-flags💾☁️🪦 [cleanable]
main [default]
* more-cleanup💾 [current]
v2gpt💾☁️

Run ``mgit legend`` for the symbol key, or ``mgit b --legend`` to append it
below the branch listing.

``💾`` means the branch is local and ``☁️`` means its configured upstream is
still present in the fetched remote refs. ``🪦 [cleanable]`` appears only
when the local branch, and any shown upstream ref, is already merged into the
default branch or is content-equivalent after a squash merge. An unpublished
branch carrying new work therefore appears as ``💾`` rather than looking
abandoned.

For a single checkout, status also prints the pending paths. Workspace output
keeps to one line per repo, so you can keep checking a directory full of
projects without turning the terminal into a log dump::
Expand Down
54 changes: 54 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Contributing

Contributions are welcome!

[tox](https://github.com/tox-dev/tox) is used for full test runs. Packaging
metadata lives in `pyproject.toml` and versions come from `setuptools-scm`.

## Development

To get going locally, simply do this:

```console
git clone https://github.com/zsimic/mgit.git
cd mgit

uv sync

# You have a venv now in ./.venv
source .venv/bin/activate
which python
which mgit
mgit

deactivate
```

## Running The Tests

Fast local checks:

```console
.venv/bin/pytest -q
ruff check
```

Full confidence check:

```console
tox
```

Useful focused tox runs:

- `tox -e py39` for the minimum supported Python version.

- `tox -e py314` for the newest supported Python version.

- `tox -e style` for packaged lint/type checks.

- `tox -e docs` for a strict `README.rst` parse check.

## Test Coverage

Run `tox`, then open `.tox/test-reports/htmlcov/index.html`.
56 changes: 0 additions & 56 deletions docs/contributing.rst

This file was deleted.

66 changes: 66 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Design Notes

This document records implementation decisions and constraints that are useful
when changing `mgit`. It is intentionally broader than a user-facing
architecture overview: add notes here when they explain how a part of the CLI
should continue to evolve.

## Cached Repository State

`GitDir` is used for one command run and lazily captures git state that can
otherwise require repeated subprocess calls.

- `GitDir.lazy_status`: A `GitStatus` snapshot produced from
`git status --porcelain=v2 --branch`. It includes pending changes and
upstream ahead/behind information. It does not classify ref-derived
conditions such as a gone upstream; command flows combine that information
with `GitRefs` where needed.

- `GitDir.lazy_refs`: A `GitRefs` snapshot centered on
`GitRefs.all_branches: dict[str, BranchInfo]`. Local branches are keyed by
their local name; unpaired remote-tracking branches use a qualified key such
as `origin/topic`. Each `BranchInfo` carries raw local/tracked-remote refs,
OIDs, trees, upstream configuration, protection, and evaluated cleanup
proofs, plus its owning `GitRefs` snapshot for lazily derived ref metadata
such as protection. The snapshot in turn owns its `GitDir`, which branches
use for exceptional proof commands. A local branch and its tracked remote
share one `BranchInfo`; a remote branch with no matching local upstream gets
a qualified entry instead of a parallel remote-ref index. Plain inspection
does not run `git remote`; the pull flow queries configured remotes only
when it needs to distinguish a missing remote.

- `GitRefs.default_branch`: The default branch derived for a particular refs
snapshot, preferring `origin/HEAD` and falling back to a visible `main` or
`master` branch.

- `BranchInfo.cleanable`, `local_cleanup`, and `remote_cleanup`: Lazily ask
their owning `GitRefs` snapshot to fill merge and cleanup proof fields.
Normal ancestry-merged branches are discovered in one
`git for-each-ref --merged=<base>` query. Candidates not found there require
individual `git merge-tree` checks to retain squash-merge/content
equivalence support. This remains lazy because flows such as checkout and
pull may inspect and then immediately invalidate a refs snapshot.

## Invalidation Requirements

The operations below may run during one lifetime of a `GitDir` instance:

| Operation | `status` | `refs` | Reason |
| --- | --- | --- | --- |
| `fetch_now()` | stale | stale | Ahead/behind and remote-tracking branches can change; pruning can alter branch presence and cleanup proof. |
| checkout | stale | stale | The checked-out branch and displayed status can change. |
| `pull()` | stale | stale | Worktree/status and remote-tracking refs may change, including when the pull fails after doing some work. |
| delete local branch | unchanged in the `groom` flow | stale | The local branch collection changes; deletion occurs after checkout. |
| delete remote branch | unchanged in the `groom` flow | stale | The remote-tracking branch collection changes. |

`default_branch` and evaluated `BranchInfo` cleanup state share the lifetime
of their owning `GitRefs` snapshot. Replacing stale `refs` discards the branch
map so raw refs and cleanup state are recomputed from the fresh snapshot.

## Current Implementation Note

`GitDir.lazy_status` and `GitDir.lazy_refs` are ordinary lazy properties backed by
`_status` and `_refs`. State-changing `GitDir` methods explicitly set the
affected backing field to `None` according to the table above. This keeps
invalidation local to the operation that makes a snapshot stale and avoids a
generic cache reset.
Loading