Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hunk-commit

Mark hunks while you review in Hunk, and move them without leaving the review — into the git index, or into a commit: a git commit, or a Jujutsu revision.

Whichever you choose, your files on disk never change — the git add -p shape, extended to committing, and to Jujutsu, which has no index.

Requirements

  • Hunk 0.20 or newer (extension API v8)
  • git, or jj — whichever the repository uses
  • For jj only: a POSIX shell, which is how the selection reaches jj. The git path has no such requirement and works on Windows.

Install

hunk extension install muzomer/hunk-commit

Or, to iterate on a checkout:

hunk diff --extension /path/to/hunk-commit

Use

Open a working-copy review (hunk diff), then:

Key Does
x Mark or unmark the hunk under the cursor — only needed to batch several
X Mark or unmark the whole file — the only way to mark a binary or oversized file
N Clear all marks
S Stage the marked hunks, or the one under the cursor — git only
C Commit the marked hunks, asking only for a summary
B Commit them with a description as well — one more question
F Add the marked hunks to a specific commit, selected from a list
D Discard the marked hunks, or the one under the cursor — reverts them in your working copy

Marked lines are painted amber in the diff — a hue the diff's own green, red, and neutral do not use. By default only the lines that will actually move are marked; context_marks (below) can extend that to the context lines around them, at the cost of making the marked region look larger than what moves, since a hunk carries up to three context lines on each side. Every one of these commands asks first — for a message, or for confirmation — names where the hunks are going, and reloads the review afterwards, so what you see is what is still unmoved.

Commands are rebindable by id in Hunk's [keybindings] table. The ids come from the folder the extension is installed into, so an install from a repository named hunk-commit gives hunk-commit.toggleHunk, hunk-commit.toggleFile, hunk-commit.clearMarks, hunk-commit.stage, hunk-commit.commit, hunk-commit.commitWithBody, hunk-commit.into, and hunk-commit.discard.

Config

# ~/.config/hunk/config.toml
[extension.hunk-commit]
context_marks = "none"  # how much of a marked hunk's context lines is marked:
                        # "none" (default), "edge" (a thin rail), "full".

There is no configured destination: C always makes a new commit and F always asks which existing one, so where a hunk lands is decided in the review rather than in a file.

Committing

C turns the marked hunks straight into a commit — a git commit on the current branch, or a new Jujutsu revision — instead of leaving them staged. It asks one question, the summary, and commits. B is the same command with a description: Hunk's input dialog holds a single line, so a body costs a second question, and only the key that promises one asks it. Cancelling any question abandons the commit, and typing the message is the confirmation, so nothing asks again afterwards.

In git it refuses, before asking anything, when something is already staged (git commit would sweep it in) or when a rebase, merge, or cherry-pick is half-finished. If a pre-commit hook rejects the commit, the marked hunks are unstaged again, so the repository is exactly as it was.

Putting hunks into an existing commit

F lists the commits you can still change and puts the marked hunks into the one you pick. Only unpushed commits are offered — @{upstream}..HEAD, or the recent history when the branch tracks nothing — so the picker cannot offer a commit that someone else may already have.

What happens next differs, and the confirmation says which:

  • In Jujutsu it happens now. jj squash moves the hunks into the revision, rebases its descendants, and records one operation that jj undo reverses.
  • In git it is deferred. A fixup! commit is added on top, naming the target by its full hash — git matches a title or a hash, and titles repeat. Nothing is rewritten until you run the git rebase --autosquash --autostash command the message gives you, at a moment you choose. --autostash is part of it because the hunks you did not mark are still in your working tree.

Discarding

D is the opposite of S: instead of moving the marked hunks somewhere, it reverts them in your working copy. The arithmetic is the same one staging uses, run the other way round — staging keeps the marked hunks and reverts the rest, discarding reverts the marked hunks and keeps the rest.

The consequence is not the same, and the confirmation says so in different words depending on where you are:

  • In a Jujutsu workspace it is recoverable. Loading a review runs jj diff, which snapshots the working copy into the operation log, so the state before the discard is already recorded. jj undo brings the changes back.
  • In a git repository it is not. Uncommitted text that gets overwritten exists nowhere else — there is no stash, no dangling object, nothing to recover from. This is the same finality as git restore -p.

Discarding always asks first; there is no path that skips the confirmation. A binary file is refused rather than guessed at, because the patch carries no record of what it held before — revert those with jj restore or git restore.

How it works

The marking half is shared. The applying half is not, because the two systems disagree about what staging is.

Git has an index, so staging is a patch applied to it. hunk diff in a git repository is a bare git diff — the working tree against the index — so a patch of the marked hunks applies exactly where it was measured, and composes with whatever was staged before. Whole-file marks go through git add instead, which handles binaries, renames, and mode changes natively.

Jujutsu has no index, so staging is a rewrite — jj split to extract the marked hunks into a new revision, or jj squash to fold them into one that already exists. Either way sub-file selection goes through jj's diff editor: it runs one with $left holding the target's content and $right holding everything the source revision changed, and whatever $right contains when the editor exits is what moves. So this extension rebuilds each partly marked file by reverting its unmarked hunks, writes the results into a staging directory, and points a generated merge-tool config at a small sh script that copies that directory into $right. Both commands read the same two directories, so the selection means the same thing to either. jj does the rest — rewriting revisions, rebasing descendants, and recording one operation, which jj undo reverses completely.

The new revision's description is passed with --message, so jj never opens an editor that would fight Hunk for the terminal.

The difference shows up in one detail: in git, a file nobody marked needs no instruction at all, because "not staged" is git's default state. In jj, a revision has to be told what stays behind.

Both paths use the same line arithmetic against the patch Hunk is already showing, so there is no re-diffing and no fuzz, and all of it stays in TypeScript where it is unit-tested. The shell script holds none of it.

What it refuses to do

Staging is refused, with nothing written, when:

  • the working copy moved on. Every marked file is checked against the patch the review was built from, line by line. Hunk reviews a snapshot, so a marked hunk can stop meaning what it meant. A mismatch stops the whole operation.
  • the two parses disagree. Hunk parses the patch to render it and assigns the hunk indexes you mark; this extension parses it again to rebuild files. If the two ever disagree about a hunk's extent, marking hunk 2 could stage something else, so staging refuses instead.
  • the review is not a working-copy review, the jj target is not a usable revset, or a path cannot be expressed in the helper's manifests (paths containing newlines).

Known limitations

  • Working-copy reviews only. Rebuilding a file reads it from the working copy, so hunk show <rev> and range diffs are not stageable.
  • No unstaging. hunk diff --staged plus a reversed patch would give git unstaging; it is not wired up. U is left free for it.
  • S is git's alone. Jujutsu has no index, so there is nothing to stage into: C and F are its two destinations.
  • Discarding is not a transaction. Every file is checked before any file is written, so a refusal changes nothing — but an error partway through the writes leaves earlier files already reverted.
  • Nothing routes hunks automatically. jj absorb and git absorb send each hunk to the commit that last touched those lines; F always asks instead, so no commit is rewritten on a guess.
  • Binary and oversized files are all-or-nothing — Hunk shows no hunks for them, so X is the only way to stage them.
  • Windows works for git, not jj. The jj helper needs a POSIX shell; the seam for a PowerShell version is src/jj/script.ts.

Compared with hunk-git-lite

hunk-git-lite also stages from inside Hunk, at file granularity, with a status pane showing staged and unstaged sections. This extension works at hunk granularity and covers Jujutsu as well, but has no status pane. They are complementary.

Development

bun install
bun test          # unit tests, plus integration tests when git / jj are on PATH
bun run typecheck

The integration tests build real git and jj repositories in temp directories and drive the real binaries end to end; each suite skips itself when its binary is missing.

Layout:

Path Holds
index.ts composition root: Hunk commands, events, highlights
src/patch/ parsing patches, rebuilding files, writing a patch back out
src/staging/ the backend port, and what each file contributes
src/git/ the index backend
src/jj/ the revision backend, and jj's diff-editor protocol
src/ui/ marks, painted highlights, wording, settings
src/workspace.ts which system a review sits in — jj wins a colocated tie

src/ never imports from index.ts, nothing under src/patch/ touches the filesystem, and neither backend knows the other exists.

License

MIT

About

Hunk extension to commit hunks in Git and Jujutsu

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages