Skip to content

Native Jujutsu (jj) support - #12

Open
CoreyKaylor wants to merge 4 commits into
mainfrom
jj
Open

CoreyKaylor wants to merge 4 commits into
mainfrom
jj

Conversation

@CoreyKaylor

Copy link
Copy Markdown
Owner

Adds native Jujutsu support (#11). In a jj workspace, review, staging, commit, log, compare and conflict resolution all run through jj, and diffbandit can act as jj's merge editor and diff editor.

This is open for jj users to try before it merges. Feedback on how the staging model feels is especially welcome.

Commits

  1. External merge and diff tool mode. Adds diffbandit merge|edit|diff, a pluggable merge host, and an editable folder diff. It doesn't depend on jj and also works as a Git mergetool.
  2. VCS backend proxy. Routes repository calls to Git or jj by repo. No behaviour change on its own, and it fixes three small Git bugs along the way.
  3. jj backend. Queues, the squash-workflow staging model, commit and amend, native conflicts, and 50_jj specs run against real jj repos. CI installs jj.
  4. Docs. README and :help diffbandit-jj.

Trying it

Point your plugin manager at this branch, for example with lazy.nvim:

{ "CoreyKaylor/diffbandit.nvim", branch = "jj" }

Then run diffbandit (or :DiffBanditLaunch) in a jj repo. To use it as jj's editor too, add this to ~/.config/jj/config.toml, with bin/diffbandit on your PATH:

[merge-tools.diffbandit]
program = "diffbandit"
merge-args = ["merge", "$base", "$left", "$right", "$output", "--path", "$path"]
edit-args = ["edit", "$left", "$right"]
diff-args = ["diff", "$left", "$right"]
merge-tool-edits-conflict-markers = false

[ui]
merge-editor = "diffbandit"
diff-editor = "diffbandit"

How staging maps to jj

  • The unstaged view shows @-→@, the same as plain jj diff.
  • Staging a hunk or file moves it from @ into the staging change. The staging change is @-, as long as it has no description and can still be edited.
  • If @- is already a described commit, your first stage creates an empty staging change under @. An existing commit is never changed by accident.
  • Amend mode stages straight into a described @-.
  • :w in the panel describes the staging change and opens a fresh one under @.

Worth trying

  • jj split, jj squash -i and jj resolve with the config above.
  • Staging hunks, committing, and amending from the panel.
  • Opening the log and a bookmark compare.
  • A colocated repo, including the vcs.prefer = "git" override.

Known limits

  • Conflicts with more than two sides, or involving a deletion, are handed off to jj resolve.
  • A merge @ lists only its conflicts.
  • The log shows commit ids rather than change ids.

Testing

  • The unit suite, including the 50_jj specs, and tests/integration/run.sh pass on each commit.
  • I checked these by hand in a terminal: jj resolve (quitting keeps the conflict, saving resolves it), jj split, and staging from the panel.

Closes #11

bin/diffbandit gains repository-free subcommands so it can serve as jj's
merge/diff editor and as a git mergetool/difftool:

  diffbandit merge BASE LEFT RIGHT OUTPUT [--path P]
  diffbandit edit LEFT RIGHT
  diffbandit diff LEFT RIGHT

The merge host now takes a pluggable host (context, result path, resolve,
after_resolve); the git host keeps the index-stage/`git add` behaviour and
Merge.start_files uses a file host that writes OUTPUT on :w. Quitting a
merge without resolving exits non-zero so the conflict is kept.

Folder diffs get an editable-right mode for diff editors (right files are
edited in place, missing ones are created on write, rows rescan on
return) plus hide patterns for jj's JJ-INSTRUCTIONS file.
Consumers now require diffbandit.vcs instead of diffbandit.git. The proxy
exposes the git module's function names and routes each call by its
root/path/queue argument, so a second backend can plug in without
touching the hosts, panel, or hunk actions. Only git is registered yet;
vcs.prefer selects a backend once more exist.

Also fixes git.current_branch (it called a local before its definition),
drops a duplicate relpath, and stops commit_metadata from running a
wasted `git log` when a revision is given. tests/run.lua accepts
DIFFBANDIT_SPECS=20,50 to run a subset of specs.
In a jj workspace (colocated or not) the vcs proxy routes to the new jj
backend, so reviews, hunk and file staging, the commit panel, log,
compare, checkout, and conflict resolution all work there.

Staging follows jj's squash workflow: an undescribed, mutable @- is the
staging change, and staging moves hunks from @ into it with
`jj diffedit --restore-descendants` (driven by a generated
non-interactive tool script), so @'s content never changes. A described
@- is treated as a commit: the first stage from the unstaged view
starts a fresh staging change instead of rewriting it, and only amend
mode stages into or re-describes it. Commit describes the staging
change and opens a new one; without one it runs `jj commit`.

The backend maps its views onto git-shaped options (unstaged @- -> @,
staged @-- -> @-, all @-- -> @, rev for any revsets) so git/sources.lua
builds diff sources unchanged; ref_labels and index_tracks_additions are
the only jj hooks there. Conflicts in @ are read from git-style markers
and resolved by writing the working copy through a jj merge host.

CI installs a pinned jj; tests/specs/50_jj.lua runs against real jj
repos (including `jj resolve` and `jj split` through bin/diffbandit) and
is skipped when jj is missing.
README and :help diffbandit-jj describe how the views, staging, commit,
amend, and conflicts map onto jj, the jj merge-tools configuration for
using bin/diffbandit as jj's merge and diff editor, and current limits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native Jujutsu (jj) support

1 participant