jj / How jj show works

How jj show works

jj show prints each revision's metadata with a template, then its diff against its parents. With no arguments it shows @; given a revset, it shows every commit in it, newest first.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. What it reads, and snapshotting
  6. 6. Algorithm and invariants
  7. Choosing the formats
  8. What a commit is diffed against
  9. Order
  10. 7. Errors and edge cases
  11. 8. Example output

In short: jj loads the workspace, which snapshots it, and evaluates the revsets (default @). It picks the template (-T, else templates.show) and the diff formats (the flags, else ui.diff-formatter). For each commit it renders the template, then diffs the merged tree of the commit's parents against the commit's own tree, with copy records from each parent. Nothing is written: no transaction, no operation.

The command is cmd_show. The help text: "Show revision metadata and diff" (source). Unlike jj log, it draws no graph and shows the diff unless --no-patch is given.

1. Command-line surface

ArgumentTypeDefaultMeaning
REVSETS (positional, repeatable), or the hidden -r REVSETSrevset@"Show changes in these revisions, compared to their parent(s)". Positional and -r values are unioned (source, source).
--reversedflagoffOlder revisions first (source).
-T, --template TEMPLATEtemplatetemplates.showHow to render each commit; every 0-argument method of Commit is a keyword (source).
--no-patchflagoffDon't show the diff. Conflicts with every diff format flag (source).
Short formats: -s/--summary, --stat, --types, --name-onlyflags, at most onenonePer-path summary, histogram, type letters, or path only (source).
Long formats: --git, --color-wordsflags, at most onenoneA Git-format or word-level diff. One short and one long format can be combined (source).
--tool NAMEstringnoneAn external diff command, or :name for a built-in format (source).
--context Nintegerdiff.git.context / diff.color-words.contextLines of context (source).
-w/--ignore-all-space, -b/--ignore-space-changeflagsoffWhitespace handling when comparing lines; show gives them their short names (source, source).

Global flags such as --at-op and --ignore-working-copy are on the CLI page.

2. Configuration

KeyDefaultEffect
templates.showbuiltin_log_detailedThe header for each commit (source): commit and change ID, bookmarks, tags, author, committer, an optional signature line, then the indented description or (no description set) (source).
ui.diff-formatter:color-wordsThe format when no diff flag is given (source). A :name picks a built-in; anything else is an external tool (source).
diff.color-words.*conflict = "materialize", max-inline-alternation = 3, context = 3Options for the default format (source).
diff.git.*context = 3, show-path-prefix = trueOptions for --git (source).
ui.conflict-marker-stylediffHow conflicted files are written out when diffed (source, source).
ui.show-cryptographic-signaturesfalseAdds a Signature: line to the default template (source).
ui.pager, ui.paginateless -FRXK, autoOutput goes through the pager (source).

3. End-to-end flow

jj show: call flow After the shared lifecycle, cmd_show parses the revsets, defaulting to @, then parses the template from -T or templates.show and builds a diff renderer from the format flags or ui.diff-formatter. It evaluates the revset to a stream of commits, collecting and reversing it with --reversed, and asks for the pager. For each commit it renders the template, then, unless --no-patch, calls show_patch: parent_tree merges the parents' trees, copy records are gathered from each parent, and show_diff writes each requested format. Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) parse the revsets none given: @ · else a union parse the template -T, else templates.show diff_renderer_for flags, else ui.diff-formatter evaluate_to_commits() a stream, newest first --reversed: collect, reverse then request the pager for each commit: template.format(commit), then show_patch unless --no-patch parent_tree() empty by index: own tree else merge the parents get_copy_records() from each parent to this commit show_diff_trees() one pass per format over diff_with_copies done: no transaction, no operation

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Revset enginereadParses and evaluates the revsets (source).revsets
Commit indexreadOrders the commits, and its changed-path data can show a commit is empty without merging trees (source).index
CommitsreadEach shown commit, for the template, and its parents.commits
TreesreadThe commit's tree and its parents' merged tree, diffed path by path.trees
Copy recordsreadFrom the backend, per parent; shown as (old => new) (source).trees
ConflictsreadMerge parents are merged recursively; conflicted files are materialized for the diff.conflicts
Viewread@, bookmarks and tags feed the revset and the template.view
Operation loguntouchedNo transaction is started.operations

5. What it reads, and snapshotting

  • Snapshot: yes. It loads the workspace with workspace_helper (source), which snapshots the working copy first (source), so jj show includes current edits to @. That snapshot may record a snapshot working copy operation; show itself records nothing.
  • Reads: the view at the current (or --at-op) operation, the index, each shown commit and its parents, their trees, file contents for the diff, and copy records.
  • Output: through the pager, requested after evaluation and before the first commit is printed (source). The template output is labelled show commit for colours (source).

6. Algorithm and invariants

Choosing the formats

diff_formats_for reads at most one short and one long format from the flags (source). --tool=:name fills the slot of that built-in's kind; any other --tool fills the long slot with an external tool. If both slots are empty, it uses ui.diff-formatter. So the list is never empty, and --stat --git prints a histogram followed by a Git diff. Each format then makes its own pass over the tree diff, in that order (source).

What a commit is diffed against

show_patch diffs parent_tree() against the commit's tree. If the index's changed-path data says the commit changed nothing, the parent tree is the commit's own tree and the diff is empty at no cost (source). A single parent's tree is used as it is. For a merge, the parents' trees are merged with their merge bases, found recursively (parent_tree, merge_commit_trees_no_resolve), and resolved where possible. The diff therefore shows only what the merge commit itself changed, not everything each side brought in.

The tree a merge commit is diffed against A merge commit M has parents P1 and P2, whose merge base is B. jj show builds the parent tree as P1 plus P2 minus B, resolves what it can, and diffs that against M's tree. For a commit with one parent, the parent tree is just that parent's tree. B (base) P1 P2 M (shown) parent tree P1 + P2 − B resolved where possible the diff shown parent tree → M's tree only M's own changes

Copies and renames come from the backend's copy records between each parent and the commit (source), filtered by the matcher, which for show is EverythingMatcher (source). The tree diff pairs them up, so a rename is printed once as file1 => file3 instead of a deletion and an addition.

Order

Commits are streamed in index order, newest first (source), not topologically grouped as in jj log. --reversed has to collect the whole stream first to flip it (source). There is no limit flag.

Invariants: each commit in the set is printed once, header then diff. An empty revset prints nothing and is not an error (test). Nothing in the repository changes.

7. Errors and edge cases

MessageWhenSource
error: a value is required for '--template <TEMPLATE>' but none was supplied, with a hint listing template aliasesjj show -T with no value.test
A clap "cannot be used with" error--no-patch with a diff flag; two short or two long formats; -w with -b.show.rs, diff_util.rs
--tool=<name> cannot be used with --<format>--tool asks for a format whose slot a flag already filled.diff_util.rs
Invalid builtin diff format: <name>--tool=:name or ui.diff-formatter names no built-in format.diff_util.rs
The tool `<name>` cannot be used for diff formatting--tool or ui.diff-formatter names a configured tool whose diff-args is empty.diff_util.rs
Failed to generate diffAn external diff tool failed.diff_util.rs
Revset or template parse errorsBad revset, -T, or templates.show.cli_util.rs
(empty output)The revset is empty, e.g. jj show 'none()'.test

8. Example output

From upstream's test_show_basic: @ deletes file1, edits file2, and adds file3 with file1's content, which the Git backend reports as a rename. The default :color-words format:

$ jj show
Commit ID: 92e687faa4e5b681937f5a9c47feaa33e6b4892c
Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp
Author   : Test User <test.user@example.com> (2001-02-03 08:05:09)
Committer: Test User <test.user@example.com> (2001-02-03 08:05:09)

    (no description set)

Modified regular file file2:
   1    1: foo
   2     : baz qux
        2: bar
        3: baz quux
Modified regular file file3 (file1 => file3):

Several revisions, from test_show_multiple_revisions. Newest first, and with --reversed oldest first (source):

$ jj show --reversed --no-patch ..
Commit ID: 456f4073ef89a8fbee17002de8cd8429f35e7dc9
Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu
Author   : Test User <test.user@example.com> (2001-02-03 08:05:08)
Committer: Test User <test.user@example.com> (2001-02-03 08:05:08)

    add file1

Commit ID: 9155a29f167b8735506b1c10858a4abb9ef80e21
Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp
Author   : Test User <test.user@example.com> (2001-02-03 08:05:09)
Committer: Test User <test.user@example.com> (2001-02-03 08:05:09)

    add file2

Commit ID: 997b28cbf801ff095e70f203f75731c955688350
Change ID: kkmpptxzrspxrzommnulwmwkkqwworpl
Author   : Test User <test.user@example.com> (2001-02-03 08:05:10)
Committer: Test User <test.user@example.com> (2001-02-03 08:05:10)

    modify file1

A custom template replaces the header entirely (source):

$ jj show -T description
a new commit