Review a branch one commit at a time in Hunk: the commit series in a pane beside the diff, the commit message above it, and keys to step between commits without leaving the window.
Hunk builds a revision review with git show --format=, which suppresses the
commit header, so a hunk show review says what changed and never what its
author said about it. This extension puts the commit back into the review.
Requires Hunk 0.19.0 or newer (extension API 6) and git on PATH.
A review in progress, reading this extension's own history: the commit series in
the left pane, the message above the diff, and n stepping between commits.
demo-native-crf22.mp4
- The reviewed commit in the review title:
hunk-commit-log 3/11 e0ced7b feat(session): load a clicked commit into the live review. The position also reacheshunk session list, so a window says which commit it holds. - A commit list on the left, oldest at the top, the reviewed commit marked
with
▸. Click any row to load that commit into the same window. - The commit message on top: subject, author, date, and the body wrapped to the pane. A body taller than the pane reports how many lines it held back.
- The message toned by what each part is: the sha recedes, a Conventional Commits type leads, prose reads in the foreground, identifiers in backticks are picked out, and trailers and indented blocks recede. The tones name theme roles rather than colours, so they follow whatever theme is active.
nandpto step to the next and previous commit in the series.
Every surface is inert outside a single-commit review: during hunk diff, a
stash review, or a patch review the panes stay closed, the keys do nothing, and
the review title is Hunk's own.
Try it against one review, with nothing installed:
hunk show HEAD --extension /path/to/hunk-commit-logKeep it, by naming the folder in ~/.config/hunk/config.toml:
[extensions]
paths = ["/path/to/hunk-commit-log"]Or install it from a git repository, which clones into
~/.config/hunk/extensions/installed/<repo-name>/:
hunk extension install <owner>/hunk-commit-logKeep the repository named hunk-commit-log. The folder name becomes the
extension id, and the id is what namespaces the command ids, the pane keys, and
the config table below; a different name silently renames all of them.
[extension.hunk-commit-log]
range = "main..HEAD" # the series to review; default: your unpushed commits
limit = 20 # most commits to gather when no range is set; max 500
messageRows = 8 # rows the message pane starts with; 3 to 60range is any revision range git rev-list accepts. With a range set, the
series is exactly that range, which is usually what a reviewer means by "this
branch".
Without a range, the series is @{upstream}..HEAD: the commits your branch
has added on top of what it has pushed, newest limit of them. That is the
work you are reviewing on a branch you are still building, and because the
range ends at the branch tip rather than at the commit you opened, n has
somewhere to go from anywhere inside it.
When there is no such work, the series is the limit commits ending at the
commit you opened. That is what you get on a branch in sync with its upstream,
on a branch with no upstream at all, on a detached HEAD, and whenever you open
a commit that has already been pushed — the reviewed commit is not among the
unpushed ones, so the series has to come from somewhere that holds it. This
fallback is the old default, and it starts the review at the newest end of the
series where only p can move.
A configured range that does not contain the reviewed commit is not an error
either: it falls the same way, straight past the unpushed commits to recent
history. The extension records why through hunk.log, which Hunk 0.19.0
collects but does not appear to display anywhere, so in practice the fallback
is unobservable: if the position looks like plain recent history rather than
your branch, the range is the thing to check. The unpushed default never logs,
because nothing was asked for that it failed to honour.
messageRows is the height the message pane opens at. Drag its lower edge
to resize it in a session; Hunk keeps that size in session state and forgets it
on quit, so this is where a taller default belongs if your commits carry long
bodies. The default is deliberately small: a one-line commit should not cost the
diff eight rows.
A range that begins with - is ignored. A repository's own .hunk/config.toml
may set this table, and the value reaches a git rev-list argument list where a
leading dash reads as an option.
| Key | Command id | Does |
|---|---|---|
n |
hunk-commit-log.next |
Next commit in the series |
p |
hunk-commit-log.previous |
Previous commit in the series |
h |
hunk-commit-log.toggle |
Show or hide the commit list |
i |
hunk-commit-log.message |
Show or hide the commit message |
I |
hunk-commit-log.expand |
Fit the pane to the message, or collapse it |
Remap them by command id in the [keybindings] table of your user config, which
is the only place Hunk reads keybindings from:
[keybindings]
"hunk-commit-log.next" = "ctrl+n"
"hunk-commit-log.previous" = false # unbind] and [ would read better than n and p, and they are already
hunk.review.nextHunk and hunk.review.previousHunk. A built-in keeps a chord
an extension asks for, so those requests would be refused and the keys would do
nothing new.
Stepping does not wrap at either end, and a burst of steps loads the commit you stopped on rather than every commit you passed through.
- The reviewed ref comes out of the review title. No extension API reports
it, and Hunk's git backend titles a revision review
<repo> show <ref>. A title that does not match that shape is a review this extension leaves alone. - The series is held, not recomputed. Stepping keeps the series it started
with and moves the position; only opening a commit outside it rebuilds it.
Recomputing from each newly loaded commit would report
N/Nat every stop. - Loading a commit goes through the session daemon. The extension API can
navigate inside a loaded changeset but cannot load a different one, so a click
or a step runs
hunk session reload <id> -- show <sha>against this window, found by its own process id rather than by--repo, which would settle for any window open on the same checkout.
- The commit list is a side pane, and Hunk drops a side pane that would squeeze the diff below its own minimum width. On a terminal too narrow for both, the review keeps its width and the list disappears until the window is wider. It comes back on its own; the pane stays open, it is only unrendered.
- The message pane has no scrolling of its own, because a pane never receives
key events.
Igrows it to fit the message instead. Dragging the lower edge andmessageRowsset the collapsed height. - Expanding is a swap between panes of a few fixed heights (8, 12, 18, 26, 36
and 50 rows), because a pane's height is fixed when it is registered and no
API resizes it afterwards.
Iopens the smallest one the message fits in, so the fit is exact to within a few rows rather than to the row. A message past fifty rows, or past what the terminal has once the diff keeps five, still shows its opening and reports the rest as a count. - Only Git, and it cannot tell that it is not. Hunk's Jujutsu and Sapling
backends title a revision review
<repo> show <rev>exactly as the Git one does, so in a colocated repository this extension would match the title and then read the series withgit rev-listfor a revset Git happens to understand:@means the working-copy commit tojjandHEADto Git. Use it in Git repositories. Where Git cannot resolve the revision at all, the extension stays inert, which is the harmless half of the same behaviour.
npm install # devDependencies only
npm run typecheck # tsc --noEmit
npm test # node --test, no test frameworkHunk loads the TypeScript directly, so there is no build step. React and the
OpenTUI packages stay in devDependencies on purpose: Hunk serves its own
copies to extension files, and a second React means a second hooks dispatcher
and a pane that never renders.
The tests run against injected fakes for git and for the Hunk CLI, so they
never touch a repository or a live window.