Skip to Content
Repository status

Repository status

Status reads the same fetch.toml / fetch.yaml and state as maintainer fetch, using the same workspace layout and pins. It includes eligible remembered paths and explicitly pinned checkouts such as ~/.dotfiles. It needs Git 2.18 or newer on PATH, but no token or network: status performs no remote discovery or fetch and writes no state. Its Git inspection disables optional locks, lazy fetch, fsmonitor, external diff and text conversion, and otherwise trusts each checkout’s Git configuration as git status does, so configured filters may run and have their own side effects. Ahead/behind counts describe local upstream refs; run maintainer fetch --apply separately to refresh them.

Usage

maintainer status # navigable table in a terminal maintainer status --format=plain # all rows, printable, no ANSI maintainer status --format=json # all fields and raw counts maintainer status --root ~/Development maintainer status --owner kamilsk --owner octomation maintainer status --config /path/to/fetch.toml maintainer status --config="" --root ./repos

The command accepts no positional arguments. With no configuration, it uses the default workspace layout under the current directory; --root selects another root. This still reads the default fetch state file.

Table columns

Repository status Β· 3 checkouts Β· 2 uncommitted Β· 1 ahead Β· 1 behind Β· 1 locked Β· 0 orphan Β· 0 errors Β· local refs (no fetch) REPOSITORY β”‚ BRANCH β”‚ UNCOMMITTED β”‚ STATUS β”‚ LOCK β”‚ PATH kamilsk/dotfiles β”‚ main* β”‚ +39/-0 Β· 3f β”‚ synced β”‚ β”‚ ~/.dotfiles kamilsk/egg β”‚ main* β”‚ +0/-0 β”‚ behind 409 β”‚ β”‚ public/kamilsk/egg octomation/maintainer β”‚ fetcher β”‚ +12/-1 Β· 2f Β· ?1 β”‚ ahead 1 β”‚ πŸ”’ β”‚ public/octomation/maintainer

The header summarizes the rows: the checkouts, then how many have uncommitted changes (tracked changes or untracked files), are ahead or behind their upstream, carry the push lock, are orphans, and failed to be inspected. Each count is a number of rows, and one row can fall into several categories. A failed inspection counts as an error, and as locked or orphan when it is one, but never as clean or as uncommitted, ahead or behind: those are unknown. The row of a scan obstruction or of an unresolved pin is not a checkout; an obstruction counts as an error. JSON output stays an array of rows, without the summary.

Path is the last column in both plain and terminal tables. Checkouts inside the effective workspace.root use paths relative to that root, including relative pins and per-owner layouts. External overrides and remembered paths under the home directory use ~/…; paths outside both root and home remain absolute. A checkout at root is shown as ., and an external checkout at home as ~. --root also changes the display base. Paths describe the actual local checkout, including missing pins and orphans, rather than a future fetch target. JSON path and the selected-path footer retain the full path.

* marks the default branch from local origin/HEAD, falling back to cached fetch metadata. +/- counts tracked lines from HEAD to the combined current index/worktree. f counts changed tracked files; ? counts untracked files, ! counts conflicted files. Binary changes have their own count. In an unborn repository the line count describes staged files against the empty tree. Untracked file contents are not added to line counts.

f, +/- and the binary count detect renames as Git does by default, regardless of the status.renames and diff.renames settings. A staged rename is one file in f, only its content changes reach +/-, and a renamed binary file is one binary change, so a pure rename reads +0/-0 Β· 1f. Git pairs only paths it tracks: a file moved without git add (git add -N is enough) is a deleted file plus an untracked one, and a file rewritten beyond the similarity threshold (50% by default) is a deleted file plus an added one. +/- compares the worktree, so a staged rename whose working copy is then rewritten that far stays one file in f but counts all of its lines as removed and added. Copies count as added files.

synced means the current branch matches its upstream; it can still have uncommitted work. A branch can be both ahead and behind. Detached HEAD, unborn branches, missing upstream configuration and deleted upstream refs are reported distinctly. Failures produce error rows, not zero/clean results. When a Git command fails, its error row names the command and quotes Git’s message, such as git status: fatal: bad config line 1 in file .git/config (exit status 128). A directory the scan cannot read and a Git repository at a prefix level of the layout are error rows too, with error: scan: … and their path; the other rows are complete, because the scan continues past them (see scan obstructions).

Lock shows πŸ”’ when any remote has a push URL exactly equal to no_push, the marker set by the dotfiles helper git lock [remote] (origin by default). Otherwise the cell is empty. It includes other remotes and multiple push URLs; the marker does not imply that every push destination is blocked. Custom push URLs, server permissions and hooks are not checked. After git unlock [remote], run status again to refresh the indicator. Fetch and branch status are independent of this marker. It is read separately after git status: when the inspection fails, the marker remains visible as long as Git can still read the configuration; a configuration that Git cannot read is an error row.

Interactive controls

The selected row and active column have separate highlights; the footer shows the full path, HEAD commit and upstream. The table is a snapshot taken at startup; run the command again to refresh it. Use up/down or j/k for rows, Page Up/Down to scroll, g/G or Home/End for first/last, and h/l to scroll horizontally. Resize adjusts the viewport.

Sorting

Select a column with Tab/Shift+Tab or left/right. Press s or Enter to sort by that column, replacing the whole sort chain. Shift+s (S) or Shift+Enter adds the column after the existing keys. Header clicks work the same way; Shift+click adds a key. Repeated activation cycles ascending β†’ descending β†’ removed. In additive mode, toggling a key keeps its priority; removing and re-adding it moves it to the end. 0 clears all sort keys, retaining the current filter.

Headers show direction and precedence (Branch ↑1, Status ↓2). Sorting covers the entire filtered collection, including rows outside the viewport. Repository and branch compare case-insensitively. Path compares the displayed path text case-sensitively. Uncommitted compares (added + deleted, changed files, untracked, binary, conflicts) numerically; status compares (ahead + behind, ahead, behind, status text). Lock sorts unmarked rows first ascending and marked rows first descending. Numeric error rows come last in both directions. Ties use repository/path for deterministic order. Clearing sorting restores repository/path order, or fuzzy relevance while searching.

Shift+Enter requires a terminal that reports modified keys. S works in legacy terminals too. Some terminals reserve Shift+mouse for native text selection; use S if Shift+click is intercepted.

Fuzzy filtering

Press / or click the search line and start typing. Matches update immediately; characters may be non-contiguous and matching ignores case (dtf finds dotfiles). Space-separated terms must all match, and can match different fields: repository, branch, uncommitted/status text, lock icon (πŸ”’), displayed or full path, upstream, or commit. With no explicit sort, results use fuzzy relevance; explicit keys take precedence. The header shows matched/total counts, and an empty result is shown explicitly. The summary has a line of its own under the title. While a filter is active, it describes the matching rows and is labelled shown:; without a filter it is labelled all:.

Enter or Escape leaves the input while keeping the filter. Ctrl+u clears the input. Outside the input, Escape clears an active filter; otherwise it exits. q and Ctrl-C exit from the table; Ctrl-C also exits from the input, where q is ordinary text. Sorting preserves the selected checkout. While a filter hides it, the first matching row is highlighted, and sorting keeps the highlighted row; clearing the filter restores the selected checkout unless a different row was selected. All sort/filter state is in memory. Plain/JSON output retains the original complete inventory; no repository or fetch state is changed.

Auto mode uses the interactive table when input and output are terminals and TERM is set to a value other than dumb. Otherwise it uses the plain table. --format=tui forces interactive mode and requires terminal input and output.

Workspace, pins and orphan rows

Per-repo paths select the active clone for mutation, while stale duplicates remain visible as orphan. Their own branch, changes and divergence are retained; plain output and TUI details identify the active path. Orphan is a management warning, not a replacement for Git status or a read error. Duplicates are checkouts of one owner/name, as named by their origin or by state; orphan rows are not counted among them. Without a specific per-repo pin, duplicates remain separate error rows; broad workspace pins do not choose a winner. A checkout that neither names (no origin, another remote name, or a local or file:// URL) is shown under its directory name and is never a duplicate: two such checkouts with the same directory name are two ordinary rows. The Path column distinguishes them; JSON and the selected-path footer also retain their full paths. ID-only pins without state can only use their local origin for display identity: status cannot verify a GitHub ID offline. Fetch performs that verification. A per-repo path that references a field status does not know offline, such as .Owner in an ID-only rule before fetch has recorded the repository, is an unresolved pin row. Status inspects nothing in its place, and the row alone does not make the command fail:

REPOSITORY β”‚ BRANCH β”‚ UNCOMMITTED β”‚ STATUS β”‚ LOCK β”‚ PATH id=42 β”‚ β€” β”‚ ? β”‚ unresolved pin β”‚ β”‚ ~/ext/{{.Owner}}/{{.Repo}}

If origin contradicts known state, status reports the mismatch and suggests fetch. Missing pins remain error rows, and so does a per-repo path that is not a valid template: status shows the rule’s path and the parse error and inspects nothing in its place. Ignore rules are respected; archived/fork filters do not hide local work.

An orphan row otherwise retains its Git status and adds a management warning. JSON orphan_reason distinguishes duplicate-pin (another checkout is active), out-of-scope (a remembered checkout is outside current management rules), and remote-gone (fetch cached a GitHub 404 by repository ID: the repository was deleted or access to it was lost). The last case includes the cached check time. Status never probes GitHub or infers remote deletion from stale refs. Search for orphan or its reason to find these rows.

Configuration

Status uses the same configuration as fetch, in order:

  1. --config <path>; --config="" disables discovery.
  2. An existing file named by $MAINTAINER_FETCH_CONFIG.
  3. ./fetch.toml, then ./fetch.yaml.
  4. $XDG_CONFIG_HOME/maintainer/fetch.toml, then fetch.yaml, falling back to ~/.config/maintainer/.

A key outside the config schema is invalid configuration, as it is for fetch.

The state location comes from defaults.state_file, or $XDG_STATE_HOME/maintainer/fetch/state.json (fallback ~/.local/state/maintainer/fetch/state.json). Missing state is an empty inventory of remembered checkouts. Status does not acquire a state lock or create state directories. --config="" still loads state from the default location.

--root changes the workspace root and rebases relative pins. State paths outside the current layout are report-only orphan rows, not recursive scan roots; legacy explicit per-repo pins remain supported. The default layout excludes research and other unrelated branches without exclusion lists. No recursive submodule scan or API discovery is performed.

Flags

FlagDefaultMeaning
--config <path>autoConfig discovery override; empty disables discovery
--root <path>config, otherwise .Override workspace root for this run
--format auto|plain|json|tuiautoSelect output mode
--owner <name>all local ownersFilter rows by owner, case-insensitively; repeatable
--concurrency <n>config, otherwise 4Parallel Git inspections; explicit value must be positive
--timeout <duration>0Whole-command time limit, including TUI; 0 is unlimited
-h, --helpShow command help

JSON output and exit codes

JSON is an array, including [] for an empty collection, with repository, absolute path (an unresolved pin keeps the path of its rule as written), branch, added, deleted, changed_files, untracked, binary, conflicts, ahead, behind, status, push_locked (boolean), and pinned. id, default_branch, commit, upstream, visibility (public, private or internal, as the fetch state recorded it), and error are omitted when unavailable or empty. Orphan rows also expose orphan_reason, optional active_path, and remote_checked_at for cached remote-gone observations saved by fetch apply. Text/TUI shows only orphan, without bracketed reason codes; the reason remains in JSON and searchable.

For example, list changed checkouts with their full paths:

maintainer status --format=json | jq '.[] | select(.changed_files > 0 or .untracked > 0) | {repository, path}'

List checkouts marked by git lock:

maintainer status --owner octomation --format=json | jq -c '.[] | select(.push_locked) | {repository, path}' # {"repository":"octomation/maintainer","path":"/Users/me/Development/public/octomation/maintainer"}
CodeMeaning
0Successful snapshot, including dirty, diverged, orphan and unresolved pin rows
1Git inspection, discovery, state or output failure
2Usage error or invalid configuration, including TUI without terminal I/O

A usage error names its cause in stderr and exits 2 before any checkout is read: an unknown flag, a flag value that does not parse, such as --concurrency=abc, an unknown --format, a --concurrency that is not positive, a negative --timeout, or an unexpected argument.

Individual checkout errors remain in the output alongside successful rows, then the command exits with 1. A fatal configuration, state or discovery failure can prevent the snapshot from being rendered.