Magit-style PORCELAIN layer for the TUI — the pure, testable half of the
C-x g status buffer. Everything runs over the native git CLI via
internal.git/run-git; nothing here touches a terminal.
Three surfaces:
status-model — one snapshot of the repo: head/upstream facts plus the
magit sections (untracked / unstaged / staged /
unmerged / stashes / unpushed / unpulled / recent
commits).stage-file!, unstage-file!, discard-file!,
commit!, push!, pull!, fetch!, branch and
stash verbs. Each returns {:ok? bool :msg str} so
the dialog can echo success/failure verbatim.status-rows — PURE projection of a model (+ the expanded-diff set)
into renderable row maps; the dialog only paints and
moves a cursor over what this returns.Keeping the model/actions/rows split pure means the whole magit feature is exercisable in tests against a throw-away repo, with zero lanterna.
Magit-style PORCELAIN layer for the TUI — the pure, testable half of the
C-x g status buffer. Everything runs over the native `git` CLI via
`internal.git/run-git`; nothing here touches a terminal.
Three surfaces:
- `status-model` — one snapshot of the repo: head/upstream facts plus the
magit sections (untracked / unstaged / staged /
unmerged / stashes / unpushed / unpulled / recent
commits).
- actions — `stage-file!`, `unstage-file!`, `discard-file!`,
`commit!`, `push!`, `pull!`, `fetch!`, branch and
stash verbs. Each returns `{:ok? bool :msg str}` so
the dialog can echo success/failure verbatim.
- `status-rows` — PURE projection of a model (+ the expanded-diff set)
into renderable row maps; the dialog only paints and
moves a cursor over what this returns.
Keeping the model/actions/rows split pure means the whole magit feature is
exercisable in tests against a throw-away repo, with zero lanterna.(catalog-entries)The workspace.filesystem catalog of the MERGED configuration — the global
~/.vis/config.yml, the project's own vis.yml and its .vis/config.yml
overlay — reduced to the entries that mount on THIS host (when / optional).
load-config-raw is memoized against the config files' mtimes, so an edited
vis.yml reaches the next status buffer without a /reload. Never throws: an
unreadable or invalid config simply declares nothing.
The `workspace.filesystem` catalog of the MERGED configuration — the global `~/.vis/config.yml`, the project's own `vis.yml` and its `.vis/config.yml` overlay — reduced to the entries that mount on THIS host (`when` / `optional`). `load-config-raw` is memoized against the config files' mtimes, so an edited `vis.yml` reaches the next status buffer without a `/reload`. Never throws: an unreadable or invalid config simply declares nothing.
(commit! root message {:keys [amend? no-verify?]})Refuses an empty message and — like magit's Nothing staged guard — a
non-amend commit over a clean index (a bare git commit failure would echo
a whole status dump). The index guard only applies when HEAD resolves:
diff --cached --quiet reports clean on an unborn branch even with staged
files.
opts: :amend? adds --amend, :no-verify? adds --no-verify so the
pre-commit / commit-msg githooks are skipped (magit's commit transient
-n Disable hooks). :no-verify? never relaxes vis' OWN :git/commit
authorization boundary — it only silences git's hooks.
Refuses an empty message and — like magit's `Nothing staged` guard — a non-amend commit over a clean index (a bare `git commit` failure would echo a whole status dump). The index guard only applies when HEAD resolves: `diff --cached --quiet` reports clean on an unborn branch even with staged files. `opts`: :amend? adds `--amend`, :no-verify? adds `--no-verify` so the pre-commit / commit-msg githooks are skipped (magit's commit transient `-n Disable hooks`). :no-verify? never relaxes vis' OWN `:git/commit` authorization boundary — it only silences git's hooks.
(commit-diff-lines root {:keys [sha]})Diff body for one commit row, as plain lines (for the TAB fold): the patch
git show produces for sha, kept from the first diff --git header down so
every file the commit touched is named.
Diff body for one commit row, as plain lines (for the TAB fold): the patch `git show` produces for `sha`, kept from the first `diff --git` header down so every file the commit touched is named.
(configured-roots ws)(configured-roots ws entries)Repo entries for the repositories DECLARED in the workspace.filesystem
catalog of vis.yml — the sibling checkouts a project binds into the session
(- id: spel / path: ~/spel). They live OUTSIDE the project tree, so
nested-roots can never discover them: the catalog is the only place they are
named. Each is labelled by its catalog id, the same name vis doctor and
jail.filesystem.allow use.
Two declared roots are skipped. A read-only one: the status buffer stages,
commits and pushes, so it is an ACTION surface and a reference tree is not
meant to be acted on. And one that holds no .git, which is every cache the
catalog grants.
Draft isolation is obeyed exactly as the tool layer obeys it. In a DRAFTED
session a not-allowed root is withheld entirely, and a copy-only /
copy-and-apply root shows the private clone minted for that draft — never
the real root — or is withheld when no clone was minted. A verb can therefore
never write through a root the draft promised to isolate.
Repo entries for the repositories DECLARED in the `workspace.filesystem` catalog of `vis.yml` — the sibling checkouts a project binds into the session (`- id: spel` / `path: ~/spel`). They live OUTSIDE the project tree, so `nested-roots` can never discover them: the catalog is the only place they are named. Each is labelled by its catalog `id`, the same name `vis doctor` and `jail.filesystem.allow` use. Two declared roots are skipped. A `read-only` one: the status buffer stages, commits and pushes, so it is an ACTION surface and a reference tree is not meant to be acted on. And one that holds no `.git`, which is every cache the catalog grants. Draft isolation is obeyed exactly as the tool layer obeys it. In a DRAFTED session a `not-allowed` root is withheld entirely, and a `copy-only` / `copy-and-apply` root shows the private clone minted for that draft — never the real root — or is withheld when no clone was minted. A verb can therefore never write through a root the draft promised to isolate.
(create-branch! root name)Create AND check out name (magit's b c).
Create AND check out `name` (magit's `b c`).
(current-branch root)Short name of the current branch (main), or nil on a detached/unborn HEAD.
Short name of the current branch (`main`), or nil on a detached/unborn HEAD.
(discard-file! root {:keys [path area]})Magit's k: throw the change away.
git clean -f)git checkout -- path) — the staged half of the
same file survives, exactly like magitMagit's `k`: throw the change away.
- untracked file → delete it (`git clean -f`)
- UNSTAGED change → restore the INDEX version in the worktree only
(`git checkout -- path`) — the staged half of the
same file survives, exactly like magit
- staged change in HEAD → restore the HEAD version in index AND worktree
- added, not in HEAD → unstage only (the file survives as untracked —
never silently delete content git has never stored).(file-diff-lines root {:keys [path area]})Diff body for one file row, as plain lines (for the TAB fold).
area picks the side: :staged → diff --cached, :unstaged/:unmerged →
diff, :untracked → the file's own content rendered as additions.
Diff body for one file row, as plain lines (for the TAB fold). `area` picks the side: :staged → `diff --cached`, :unstaged/:unmerged → `diff`, :untracked → the file's own content rendered as additions.
(first-selectable rows idx)Index of the first selectable row at-or-after idx (wrapping search both
directions handled by the caller); nil when nothing is selectable.
Index of the first selectable row at-or-after `idx` (wrapping search both directions handled by the caller); nil when nothing is selectable.
(gerrit-push! root {:keys [remote branch topic dry-run? no-verify?]})Push HEAD to Gerrit for review (refs/for/<branch>) — a regular push, just
carrying an optional Gerrit topic. opts: :remote :branch :topic :dry-run?
:no-verify?. remote/branch default to the detected Gerrit remote and
upstream branch.
Push HEAD to Gerrit for review (`refs/for/<branch>`) — a regular push, just carrying an optional Gerrit topic. `opts`: :remote :branch :topic :dry-run? :no-verify?. `remote`/`branch` default to the detected Gerrit remote and upstream branch.
(gerrit-remote root)Name of the remote that talks to a Gerrit server, or nil. Prefers an explicit
gerrit remote, then any remote whose URL smells like Gerrit, then falls back
to the first remote when a .gitreview file is present.
Name of the remote that talks to a Gerrit server, or nil. Prefers an explicit `gerrit` remote, then any remote whose URL smells like Gerrit, then falls back to the first remote when a `.gitreview` file is present.
(gerrit-target-branch root)Branch a Gerrit review targets: the upstream branch (minus its remote), else
the current branch, else master.
Branch a Gerrit review targets: the upstream branch (minus its remote), else the current branch, else `master`.
(gerrit? root)True when this repo appears to target a Gerrit server.
True when this repo appears to target a Gerrit server.
(head-rows {:keys [branch detached? head head-subject upstream? upstream ahead
behind]})The Head: / Merge: preamble of the status buffer.
The `Head:` / `Merge:` preamble of the status buffer.
(head-subject root)Subject line of HEAD's commit, or nil on an unborn branch.
Subject line of HEAD's commit, or nil on an unborn branch.
(last-commit-message root)Full message of HEAD's commit (for amend pre-fill), or nil.
Full message of HEAD's commit (for amend pre-fill), or nil.
(load-repos repo-entries)Attach a fresh :model snapshot to every repo entry — one status-model
read per root — then DROP every root that is not a git repository (:model
nil). A non-repo root has nothing to show, so it never earns a header in the
multi-root buffer. The refresh path of a multi-root buffer.
The reads run in PARALLEL (pmap, order preserved): every root costs a
handful of git subprocesses, and a dozen roots refreshed one after another is
what would make the buffer feel slow after each verb.
Fallback: when NOT ONE root is a git repository the first entry is kept (with
its nil :model) so a repo-less session still renders the single-root
Not a git repository empty state instead of a blank buffer.
Attach a fresh `:model` snapshot to every repo entry — one `status-model` read per root — then DROP every root that is not a git repository (`:model` nil). A non-repo root has nothing to show, so it never earns a header in the multi-root buffer. The refresh path of a multi-root buffer. The reads run in PARALLEL (`pmap`, order preserved): every root costs a handful of git subprocesses, and a dozen roots refreshed one after another is what would make the buffer feel slow after each verb. Fallback: when NOT ONE root is a git repository the first entry is kept (with its nil `:model`) so a repo-less session still renders the single-root `Not a git repository` empty state instead of a blank buffer.
(local-branches root)Local branches as [{:name :current?}], current first.
Local branches as `[{:name :current?}]`, current first.
(log-graph-lines root)(log-graph-lines root {:keys [all? max] :or {max 250}})Pretty git log --graph for the fullscreen log viewer, as ANSI-colored
lines (git's own --color=always graph). opts:
:all? include every ref, not just HEAD's history (default false):max commit cap (default 250).
Returns a vec of lines; a one-line notice when the repo has no commits.Pretty `git log --graph` for the fullscreen log viewer, as ANSI-colored lines (git's own `--color=always` graph). `opts`: - `:all?` include every ref, not just HEAD's history (default false) - `:max` commit cap (default 250). Returns a vec of lines; a one-line notice when the repo has no commits.
(multi-status-rows repos expanded diff-fn)Rows for one OR many repos. repos is [{:root :label :draft? :model}]
(:model a status-model, nil outside a git repo). ONE repo renders
exactly like status-rows — the single-root buffer looks unchanged —
while several repos each get a :repo header row. EVERY row is tagged
with its :root so the dialog's verbs route to the right repo.
In a MULTI-root buffer only the repos with something to act on unfold
(repo-open?); a clean repo is its header line alone until TAB opens it, so
a mega-repo of a dozen clones opens as a dozen readable lines instead of a
dozen commit logs.
expanded is a set of [root area path] triples — [root :repo nil] flips
a repo's own fold; diff-fn receives the root-tagged file row.
Rows for one OR many repos. `repos` is `[{:root :label :draft? :model}]`
(`:model` a `status-model`, nil outside a git repo). ONE repo renders
exactly like `status-rows` — the single-root buffer looks unchanged —
while several repos each get a `:repo` header row. EVERY row is tagged
with its `:root` so the dialog's verbs route to the right repo.
In a MULTI-root buffer only the repos with something to act on unfold
(`repo-open?`); a clean repo is its header line alone until TAB opens it, so
a mega-repo of a dozen clones opens as a dozen readable lines instead of a
dozen commit logs.
`expanded` is a set of `[root area path]` triples — `[root :repo nil]` flips
a repo's own fold; `diff-fn` receives the root-tagged file row.(nested-roots root)(nested-roots root opts)Repo entries for EVERY Git repository nested BELOW root — the clones a
mega-repo keeps under repositories/, all of them, not a first dozen. The
root itself is dropped — workspace-roots already owns it — and each entry
is labelled by its path RELATIVE to the session root, so a mega-repo reads as
repositories/svar and not as a second svar. Never throws: a missing or
unreadable root yields no entries.
Repo entries for EVERY Git repository nested BELOW `root` — the clones a mega-repo keeps under `repositories/`, all of them, not a first dozen. The root itself is dropped — `workspace-roots` already owns it — and each entry is labelled by its path RELATIVE to the session root, so a mega-repo reads as `repositories/svar` and not as a second `svar`. Never throws: a missing or unreadable root yields no entries.
Ceiling on the repositories the discovery WALK may find below the session
root. NOT a display limit: every repository the walk finds earns its own
header, however many that is — a mega-repo of forty clones opens as forty
rows. A walk over an unknown tree has to terminate somewhere, and when this
bound (or the budget below) does bite, the buffer SAYS so
(nested-scan-truncated?) instead of quietly showing fewer repositories.
Ceiling on the repositories the discovery WALK may find below the session root. NOT a display limit: every repository the walk finds earns its own header, however many that is — a mega-repo of forty clones opens as forty rows. A walk over an unknown tree has to terminate somewhere, and when this bound (or the budget below) does bite, the buffer SAYS so (`nested-scan-truncated?`) instead of quietly showing fewer repositories.
(nested-scan-truncated? root)(nested-scan-truncated? root opts)Did discovery stop on its own budget rather than on the end of the tree? Then
repositories exist below root that this buffer is NOT showing, and the
dialog title carries scan truncated: a short list must never be mistaken
for a complete one. Reads the SAME cached scan nested-roots did, so it
costs a map lookup.
Did discovery stop on its own budget rather than on the end of the tree? Then repositories exist below `root` that this buffer is NOT showing, and the dialog title carries `scan truncated`: a short list must never be mistaken for a complete one. Reads the SAME cached scan `nested-roots` did, so it costs a map lookup.
(next-section rows idx dir)Index of the nearest :section/:repo header from idx moving by dir
(+1/-1); idx unchanged when there is none that way. Magit's n/p
section-to-section jump.
Index of the nearest `:section`/`:repo` header from `idx` moving by `dir` (+1/-1); `idx` unchanged when there is none that way. Magit's `n`/`p` section-to-section jump.
(next-selectable rows idx dir)Index of the nearest selectable row moving from idx by dir (+1/-1);
returns idx unchanged when there is none further that way.
Index of the nearest selectable row moving from `idx` by `dir` (+1/-1); returns `idx` unchanged when there is none further that way.
(push! root {:keys [set-upstream? force? remote dry-run? no-verify?]})opts: :remote (default "origin"), :set-upstream? adds -u <remote> <branch>,
:force? uses --force-with-lease (never bare --force), :dry-run? adds
--dry-run, :no-verify? adds --no-verify to skip pre-push hooks.
`opts`: :remote (default "origin"), :set-upstream? adds `-u <remote> <branch>`, :force? uses `--force-with-lease` (never bare --force), :dry-run? adds `--dry-run`, :no-verify? adds `--no-verify` to skip pre-push hooks.
(range-commits root range-spec)Commits in range-spec (e.g. "@{upstream}..HEAD") as [{:sha :subject}],
newest first. Empty when the range is empty or unresolvable.
Commits in `range-spec` (e.g. "@{upstream}..HEAD") as `[{:sha :subject}]`,
newest first. Empty when the range is empty or unresolvable.(recent-commits root n)Latest n commits as [{:sha :subject}], newest first.
Latest `n` commits as `[{:sha :subject}]`, newest first.
(refs-for-spec branch topic)PURE: the Gerrit push refspec HEAD:refs/for/<branch>, with an optional
%topic=<topic> appended when topic is non-blank.
PURE: the Gerrit push refspec `HEAD:refs/for/<branch>`, with an optional `%topic=<topic>` appended when `topic` is non-blank.
(remotes root)Configured remotes as [{:name :url}] (the PUSH url), in git's own order.
Deduplicated so a remote with distinct fetch/push urls shows once.
Configured remotes as `[{:name :url}]` (the PUSH url), in git's own order.
Deduplicated so a remote with distinct fetch/push urls shows once.(repo-dirty? {:keys [untracked unstaged staged unmerged unpushed unpulled]})Has this repo anything to show? Working-tree changes, a conflict, or commits waiting to be pushed or pulled. A clean repo's sections are all empty except its recent history, which is why the multi-root buffer folds it shut.
Has this repo anything to show? Working-tree changes, a conflict, or commits waiting to be pushed or pulled. A clean repo's sections are all empty except its recent history, which is why the multi-root buffer folds it shut.
(repo-open? expanded root model)Is this repo's section stack unfolded in a multi-root buffer? A repo with
something to act on opens by default and a clean one shows as its header
alone; TAB on the header flips either way by putting [root :repo nil] in
expanded — the same flip-a-default rule section-open? uses.
Is this repo's section stack unfolded in a multi-root buffer? A repo with something to act on opens by default and a clean one shows as its header alone; TAB on the header flips either way by putting `[root :repo nil]` in `expanded` — the same flip-a-default rule `section-open?` uses.
(repo-row {:keys [label root draft? model collapsed?]})The repo header row a multi-root buffer shows above each repo's sections: a
fold marker, the repo's label, and repo-summary — so a FOLDED repo still
says which branch it is on and how dirty it is.
The repo header row a multi-root buffer shows above each repo's sections: a fold marker, the repo's label, and `repo-summary` — so a FOLDED repo still says which branch it is on and how dirty it is.
(repo-summary {:keys [branch detached? head untracked unstaged staged unmerged
ahead behind]
:as model})One line of state for a repo header: branch (or detached sha), then the
working-tree buckets that hold something — clean when none do — then
ahead/behind. Same vocabulary as the Head:/Merge: preamble, so a folded
repo reads like the buffer it opens into.
One line of state for a repo header: branch (or detached sha), then the working-tree buckets that hold something — `clean` when none do — then ahead/behind. Same vocabulary as the `Head:`/`Merge:` preamble, so a folded repo reads like the buffer it opens into.
(section-of rows section-idx)The [area path]-bearing rows covered by a :section header row —
used for section-wide stage/unstage (s/u on the header). Stops at the
next :section OR :repo header and only matches rows of the SAME repo
(:root), so a multi-root buffer never bleeds one repo's files into
another's section.
The `[area path]`-bearing rows covered by a `:section` header row — used for section-wide stage/unstage (`s`/`u` on the header). Stops at the next `:section` OR `:repo` header and only matches rows of the SAME repo (`:root`), so a multi-root buffer never bleeds one repo's files into another's section.
(section-open? expanded area)Is area's section currently expanded? expanded carries [:section area]
toggle keys that FLIP a section from its default — a default-collapsed
section (stash list, commit log) opens when its key is present; a
default-open working-tree section closes when its key is present.
Is `area`'s section currently expanded? `expanded` carries `[:section area]` toggle keys that FLIP a section from its default — a default-collapsed section (stash list, commit log) opens when its key is present; a default-open working-tree section closes when its key is present.
(selectable? {:keys [kind stageable?]})Rows the dialog cursor can land on. A :diff row is landable only when it is
:stageable? (a hunk of a staged/unstaged file), so plain diff peeks stay
scroll-only.
Rows the dialog cursor can land on. A `:diff` row is landable only when it is `:stageable?` (a hunk of a staged/unstaged file), so plain diff peeks stay scroll-only.
(session-roots ws fallback-root)(session-roots ws fallback-root catalog)Every repository the status buffer shows for a session, in one list:
workspace-roots) — it owns the title and the
fallback root, so it stays FIRST;nested-roots) — the clones a
mega-repo keeps in a repositories/ folder, or any repo checked out inside
the project outside .gitmodules;vis.yml's workspace.filesystem catalog
(configured-roots) — the sibling checkouts bound to the session from
elsewhere on the filesystem.The list is DEDUPLICATED twice over, first occurrence winning. By repository
(distinct-roots): a sibling that is also nested, a root declared under
another spelling of its directory, and the trunk a drafted session already
shows as its clone each earn exactly one header. Then by NAME
(unique-labels): two repos that would render the same header text are told
apart by as much of their path as it takes, because the header is all the
cursor has to say which repository a verb is about to stage, commit or push.
Roots that turn out not to be git repositories are dropped later, by
load-repos. Pass catalog explicitly to resolve against something other
than the live config.
Every repository the status buffer shows for a session, in one list: 1. the primary workspace root (`workspace-roots`) — it owns the title and the fallback root, so it stays FIRST; 2. every Git repository nested BELOW it (`nested-roots`) — the clones a mega-repo keeps in a `repositories/` folder, or any repo checked out inside the project outside `.gitmodules`; 3. every repository DECLARED in `vis.yml`'s `workspace.filesystem` catalog (`configured-roots`) — the sibling checkouts bound to the session from elsewhere on the filesystem. The list is DEDUPLICATED twice over, first occurrence winning. By repository (`distinct-roots`): a sibling that is also nested, a root declared under another spelling of its directory, and the trunk a drafted session already shows as its clone each earn exactly one header. Then by NAME (`unique-labels`): two repos that would render the same header text are told apart by as much of their path as it takes, because the header is all the cursor has to say which repository a verb is about to stage, commit or push. Roots that turn out not to be git repositories are dropped later, by `load-repos`. Pass `catalog` explicitly to resolve against something other than the live config.
(split-diff-hunks lines)PURE: split a unified diff (as lines, WITH its diff --git/index/---/
+++ preamble) into {:header [lines] :hunks [[lines] ...]} — the preamble
shared by every hunk, then one line-vector per @@ hunk. A valid one-hunk
patch for hunk N is therefore (into header (nth hunks N)), which git apply
accepts. Header is everything before the first @@; each hunk runs from its
@@ line up to (not including) the next one.
PURE: split a unified diff (as `lines`, WITH its `diff --git`/`index`/`--- `/
`+++ ` preamble) into `{:header [lines] :hunks [[lines] ...]}` — the preamble
shared by every hunk, then one line-vector per `@@` hunk. A valid one-hunk
patch for hunk N is therefore `(into header (nth hunks N))`, which `git apply`
accepts. Header is everything before the first `@@`; each hunk runs from its
`@@` line up to (not including) the next one.(stage-hunk! root {:keys [path hunk]})Magit's s on a diff HUNK: stage just hunk hunk (0-based, in @@ order) of
path's UNSTAGED diff — reconstruct that one hunk into a patch and
git apply --cached it, leaving the file's other hunks unstaged.
Magit's `s` on a diff HUNK: stage just hunk `hunk` (0-based, in `@@` order) of `path`'s UNSTAGED diff — reconstruct that one hunk into a patch and `git apply --cached` it, leaving the file's other hunks unstaged.
(stash-diff-lines root {:keys [ref]})Diff body for one stash row, as plain lines (for the TAB fold): the patch
git stash show -p produces for ref, from the first diff --git header
down so every file the stash touched is named.
Diff body for one stash row, as plain lines (for the TAB fold): the patch `git stash show -p` produces for `ref`, from the first `diff --git` header down so every file the stash touched is named.
(stash-push! root message)Stash the working tree INCLUDING untracked files (the least surprising default for a status-buffer 'stash everything' verb). Git exits 0 with 'No local changes to save' when there is nothing to stash — surface that as a failure so the dialog doesn't claim success.
Stash the working tree INCLUDING untracked files (the least surprising default for a status-buffer 'stash everything' verb). Git exits 0 with 'No local changes to save' when there is nothing to stash — surface that as a failure so the dialog doesn't claim success.
(stashes root)Stash list as [{:ref "stash@{0}" :message}], newest first.
Stash list as `[{:ref "stash@{0}" :message}]`, newest first.
(status-model root)One full magit snapshot of the repo at root, or nil outside a repo.
{:branch :detached? :head :upstream? :upstream :ahead :behind
:head-subject
:untracked [path …]
:unstaged [{:path :code} …] ;; worktree side (porcelain Y)
:staged [{:path :code} …] ;; index side (porcelain X)
:unmerged [{:path :code} …]
:stashes [{:ref :message} …]
:unpushed [{:sha :subject} …] ;; @{upstream}..HEAD — magit's Unmerged into
:unpulled [{:sha :subject} …] ;; HEAD..@{upstream} — magit's Unpulled from
:commits [{:sha :subject} …]}
One full magit snapshot of the repo at `root`, or nil outside a repo.
{:branch :detached? :head :upstream? :upstream :ahead :behind
:head-subject
:untracked [path …]
:unstaged [{:path :code} …] ;; worktree side (porcelain Y)
:staged [{:path :code} …] ;; index side (porcelain X)
:unmerged [{:path :code} …]
:stashes [{:ref :message} …]
:unpushed [{:sha :subject} …] ;; @{upstream}..HEAD — magit's `Unmerged into`
:unpulled [{:sha :subject} …] ;; HEAD..@{upstream} — magit's `Unpulled from`
:commits [{:sha :subject} …]}(status-rows model expanded)(status-rows {:keys [untracked unstaged staged unmerged stashes commits upstream
unpushed unpulled]
:as model}
expanded
diff-fn)PURE projection: model (+ expanded set of [area path], + optional
diff-fn row→lines) → renderable rows. Every row is
{:kind :info|:section|:file|:diff|:stash|:commit|:blank :text …}.
Section order mirrors magit: unpushed commits render as
Unmerged into <upstream> and Recent commits appears ONLY when nothing
is unpushed (magit's unpushed-to-upstream-or-recent); Unpulled from <upstream> closes the buffer when the branch is behind.
PURE projection: model (+ `expanded` set of `[area path]`, + optional
`diff-fn` row→lines) → renderable rows. Every row is
`{:kind :info|:section|:file|:diff|:stash|:commit|:blank :text …}`.
Section order mirrors magit: unpushed commits render as
`Unmerged into <upstream>` and `Recent commits` appears ONLY when nothing
is unpushed (magit's `unpushed-to-upstream-or-recent`); `Unpulled from
<upstream>` closes the buffer when the branch is behind.(unstage-file! root path)Magit's u: git reset -q -- path. Unlike restore --staged this also
works on an unborn HEAD (before the first commit), exactly like magit.
Magit's `u`: `git reset -q -- path`. Unlike `restore --staged` this also works on an unborn HEAD (before the first commit), exactly like magit.
(unstage-hunk! root {:keys [path hunk]})Magit's u on a staged diff HUNK: unstage just hunk hunk (0-based) of
path's STAGED diff — reverse-apply that one hunk to the index.
Magit's `u` on a staged diff HUNK: unstage just hunk `hunk` (0-based) of `path`'s STAGED diff — reverse-apply that one hunk to the index.
(upstream-name root)Short name of the current branch's upstream (origin/main), or nil.
Short name of the current branch's upstream (`origin/main`), or nil.
(visit-file-lines root path)Working-tree content of path (relative to root) as plain lines for the
fullscreen RET-visit viewer, or nil when it isn't a readable regular file
(a deleted/renamed entry has no working-tree body — the caller falls back to
the diff).
Working-tree content of `path` (relative to `root`) as plain lines for the fullscreen `RET`-visit viewer, or nil when it isn't a readable regular file (a deleted/renamed entry has no working-tree body — the caller falls back to the diff).
(workspace-roots ws fallback-root)The single repository entry the magit buffer shows for a session. A draft's
:root is its working clone and :repo-root remains its trunk. Accepts the
gateway workspace map in either in-process kebab or canonical snake shape.
The single repository entry the magit buffer shows for a session. A draft's `:root` is its working clone and `:repo-root` remains its trunk. Accepts the gateway workspace map in either in-process kebab or canonical snake shape.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |