@@ -9,7 +9,7 @@ Nothing is implicit beyond the language itself. Every shape, builtin, and
99command is declared or defined in one user-owned file, `~/.config/j/config.j`,
1010written in the language. The binary provides the language (its syntax, its
1111operators and their fixities, its primitive kinds), the implementations of the
12−declared builtins, ten reserved words, and the bridge to jj repositories.
12+declared builtins, eleven reserved words, and the bridge to jj repositories.
1313`j` does not require the `jj` binary; it uses jj's on-disk formats through
1414jj-lib.
1515
@@ -40,15 +40,19 @@ echo EXPRESSION | j
4040 `::`, `or`, `.`, and the literals `%name`, `@id`, `./a/b` are all safe
4141 unquoted.
42423. If arguments are present and rule 1 also applies, exit 2 with
43− `j: expression given both as arguments and on stdin`.
43+ `j: expression given both as arguments and on stdin`. One byte is
44+ sufficient to reject the invocation; the pipe need not reach EOF.
44454. If neither is present, or the input is only whitespace, print a concise
4546 usage and getting-started guide to stderr and exit 2. The guide points to
4647 `j help`, `j commands`, and commands for starting or inspecting a repository.
4748 It does not read or create configuration or open a repository.
48−5. `--help` and `-h` are aliases for the reserved command `help` only when
49− they are the first whitespace-separated word (§1.1). There are no other
50− flags or trailing help flags: `j 'new --help'` is an expression whose
51− `--help` is a language comment, and `j '"--help"'` prints text.
49+5. `--help` and `-h` as the first word alias `help` (§1.1). A separate
50+ argument equal to either flag after the first argument requests help for
51+ the first word, before repository access or evaluation: `j abandon --help`
52+ is `j help abandon`. An initial `help`, `--help`, or `-h` followed by
53+ another help flag opens general help. Other flags are not supported.
54+ Language comments embedded within a single argument or stdin are unchanged:
55+ `j 'new --help'` executes `new`, and `j '"--help"'` prints text.
52566. The expression is text: an argument that is not valid UTF-8 is a usage
5357 error (exit 2) naming it, and so is an expression on standard input that
5458 is not.
@@ -57,13 +61,14 @@ echo EXPRESSION | j
5761
5862Before the expression is parsed, its text is trimmed of leading and trailing
5963whitespace and split on whitespace into words. If the first word is one of the
60−ten reserved words below, the whole text is a reserved command. A first word
64+eleven reserved words below, the whole text is a reserved command. A first word
6165of `--help` or `-h` is treated as `help`.
6266
6367| command | operation |
6468|---|---|
6569| `help [NAME]` | show the command guide, or help for a reserved command or config name; §1.1.1 |
66−| `commands [QUERY]` | list the vocabulary, optionally searching names, signatures, and comments; §1.1.1 |
70+| `commands [QUERY]` | show common tasks, search the vocabulary, or use `all` for the full catalog; §1.1.1 |
71+| `setup [NAME EMAIL]` | create the complete reference configuration, optionally setting identity; §6.1.1 |
6772| `init` | create a repository in the current directory; §7.8 |
6873| `clone URL [DIR]` | clone `URL` into `DIR` (default: the repository's name in `URL`); §7.8 |
6974| `remote URL` | set the URL of the remote `origin`, creating it if absent; §7.8 |
@@ -74,8 +79,12 @@ of `--help` or `-h` is treated as `help`.
7479| `ops` | print the operation log; §7.7 |
7580
7681For every reserved command except `push` and `commands`, the remaining words
77−are literal arguments (no quoting, no escaping); a wrong number is a usage
78−error (exit 2) and the language is not involved. `commands` takes the remaining
82+are literal arguments, and the language is not involved. When the first
83+shell argument is the command name, the remaining shell argument boundaries
84+are preserved, so `j clone '/path/to/source repo' 'my project'` accepts both
85+paths. A command supplied as one string or on stdin is split on whitespace;
86+it has no additional quote or escape syntax. A wrong argument count is a
87+usage error (exit 2). `commands` takes the remaining
7988trimmed text as its optional search query, including spaces. For `push`, the
8089remainder of the text is an expression, evaluated as in §1.2 steps 2–7 but
8190against the repository as recorded, without snapshotting the working directory;
@@ -95,11 +104,18 @@ for that reserved command or for a name in the current configuration, including
95104its signature when present and available documentation. `--help [NAME]` and
96105`-h [NAME]` are equivalent to `help [NAME]`. These successful requests exit 0.
97106An unknown name is a usage error (exit 2) with guidance to use `j commands`.
98−
99−`commands` lists the reserved commands and the vocabulary actually declared or
100−defined in the current configuration, including custom definitions without
101−signatures. `commands QUERY` searches the catalog case-insensitively by name,
102−signature, and documentation comments. The catalog does not present an
107+`j help config` is a built-in topic showing the configuration path, identity
108+setup, and repair instructions. It is available even with invalid configuration.
109+
110+`commands` shows a concise catalog of common tasks and their commands.
111+`commands all` lists the reserved commands and vocabulary actually declared
112+or defined in the current configuration, including custom definitions without
113+signatures. The concise catalog includes a bounded selection of custom
114+definitions and points to the full list. `commands QUERY` searches the catalog case-insensitively by name,
115+signature, documentation comments, and common task synonyms such as `commit`,
116+`checkout`, and `branch`, ranking direct matches and everyday commands first.
117+The `stash` query explains the save/new workflow when no custom `stash`
118+definition exists. The catalog does not present an
103119undeclared builtin as available merely because the binary implements it.
104120
105121General help and help for reserved commands do not depend on configuration and
@@ -130,7 +146,8 @@ resolves commit ids, snapshots the working directory, or records operations.
1301467. If `v` is a function, apply it to `r`, once, and let `v` be the result.
1311478. If `v` is a `Repo` (§4.4): if it equals the repository value *as loaded,
132148 before the snapshot of §7.4*, do nothing at all; otherwise persist it
133− (§7.5) and check out its focused commit's files (§7.4). Print nothing.
149+ (§7.5) and check out its focused commit's files (§7.4). Print nothing to stdout;
150+ interactive stderr reports the outcome and focus (§1.5).
134151 Exit 0.
1351529. Otherwise display `v` (§5.1) on stdout. Persist nothing. Exit 0.
136153
@@ -140,7 +157,7 @@ persist nothing, leave the working directory untouched, and exit 1.
140157Step 7 applies exactly once. If the result of applying a function to `r` is
141158again a function, that function is displayed, not applied again.
142159
143−Step 8's equality test means `j id` is a no-op when the working directory is
160+Step 8's equality test means `j save` (the reference alias for `id`) is a no-op when the working directory is
144161unchanged and records it when it is not: the snapshot makes `r` differ from
145162the loaded value, and that difference is what gets persisted. The test may be
146163implemented by comparing commit and tree hashes rather than values.
@@ -152,9 +169,11 @@ Two consequences worth knowing:
152169 what a rebase would produce without doing it. `validate` in the chain,
153170 `j tree . validate . rebase trunk`, additionally raises whatever
154171 persistence would refuse, so the preview is of something that could
155− actually be persisted.
156−- **Do, then look.** `j squash` prints nothing. `j squash && j tree` is the
157− idiom; there is no flag for it.
172+ actually be persisted. The reference `preview EDIT` is the convenient
173+ form of `tree . validate . EDIT`: `j 'preview (rebase trunk)'`. Validation
174+ alone returns a `Repo`, so `validate . EDIT` persists a changed result.
175+- **Do, then look.** `j squash` reports the resulting focus on an interactive
176+ terminal. `j squash && j tree` displays the whole tree; there is no flag for it.
158177
159178### 1.3 Interrupts
160179
@@ -207,6 +226,49 @@ is in `goto`. A builtin is not a definition, so a crash in one is in
207226whichever definition applied it, and with none executing the line is only
208227` from <expression>`.
209228
229+### 1.5 Terminal presentation
230+
231+Automatic colors are used only on a terminal, and disabled by `TERM=dumb`
232+or a nonempty `NO_COLOR`. Diagnostics use the stderr terminal; displayed
233+values use stdout. Error prefixes are bright red, informational prefixes
234+bright cyan, and interactive successful commands have a green checkmark.
235+Color adds emphasis without replacing the text of errors or values.
236+Successful edits report `Saved changes` or `No changes`, followed by the
237+resulting focus's copyable, unique `@` prefix and first description line
238+(`(no description)` when empty). Setup reports the effective author identity.
239+Undo and redo identify the affected operation and restored focus.
240+After a saved edit, conflicts newly introduced anywhere in the history and
241+conflicts in the checked-out focus produce a warning with counts, paths, and
242+resolution guidance. This warning remains visible when output is redirected;
243+the operation still succeeds. Guidance identifies `j conflicts`, editing
244+conflict markers, recording the resolution with `j save` or `j id`, and
245+checking again. For another commit, navigate with `by` first.
246+
247+When both stdout and stderr are capable terminals, repository operations,
248+configuration evaluation, and expression evaluation (including implicit application)
249+may show a temporary, color-cycling progress line on stderr. It starts after
250+140 ms, advances every 75 ms, and names the current operation alongside a
251+spinner, a bouncing activity bar, and elapsed time since that operation began.
252+The bar is indeterminate and does not estimate completion. Each frame fits
253+the current stderr terminal width with a spare column, omitting the bar and
254+then the time before truncating the operation's name by display width. The
255+line is cleared before the command reports its result.
256+Fast operations never wait for a progress frame. It does not hide
257+the cursor or change terminal modes. Signal behavior remains as in §1.3.
258+Before an external renderer inherits stderr, progress is cleared and stopped
259+for the remainder of the run so even an unterminated diagnostic stays visible.
260+
261+Colored multiline output is revealed in at most seven batches, with at
262+most 96 ms of total pacing. Its bytes and row order are unchanged. Plain
263+text is written directly. `J_ANIMATIONS=never` (also `off`, `false`, or `0`,
264+case-insensitive) disables all motion while retaining colors. Motion is
265+also disabled by nonempty `NO_COLOR` or `CI`, `TERM=dumb`, or either output
266+stream being redirected. Success messages are omitted only when `CI` is
267+nonempty or either output stream is redirected. `NO_COLOR` and `TERM=dumb`
268+retain plain-text success messages on terminals; `J_ANIMATIONS` alone does
269+not suppress them.
270+Broken pipes and failed writes keep the behavior in §1.4.
271+
210272---
211273
212274## 2. Values
@@ -466,7 +528,9 @@ negation. Ordering operators are defined on `Int` only and crash otherwise.
466528
467529`if`, `or`, `&&`, and `||` do not evaluate all operands. `&&` and `||`
468530short-circuit. The interpreter recognises `&&` and `||` by name; `or` is a
469−keyword.
531+keyword. Like other builtins, `&&` and `||` must be declared. Their contracts
532+check each evaluated operand and the final result; a skipped operand is
533+neither evaluated nor checked.
470534
471535`a or b` evaluates `a`. If that crashes, the crash is discarded and `b` is
472536evaluated and returned. Otherwise, if the value of `a` is a function, `b` is
@@ -532,17 +596,19 @@ as in §4.13.
532596| `replay onto ch` | replay the change `ch` (`{ from, to }`) onto the snapshot `onto`; §7.3 |
533597| `unresolved b` | `true` iff the blob is a conflict |
534598| `blob t` | a resolved regular-file blob with content `t` |
599+| `blobHex kind hex` | a resolved blob of kind `"regular"`, `"executable"`, or `"symlink"`, with bytes decoded from an even-length hexadecimal text; accepts either letter case, crashes for an unknown kind or invalid hex |
535600| `text b` | the content of a blob as text; for an unresolved blob, its conflict-marker rendering |
536601| `by i repo` | the repo refocused on the commit with id `i`; crashes if absent. Backed by the change-id index; its reference definition is in §10 |
537602| `lookupCommit i repo` | `(by i repo).root`, indexed once per repository value without constructing a location for each lookup. Duplicate ids select the first preorder match; malformed trees retain `by`'s traversal behavior |
538603| `lookupContent p s` | `(head (filter (\e -> e.path == p) s)).content or blob ""`, with an index for ordinary paths. The full filter's errors and first-match behavior are preserved; the reference `contentAt` uses this builtin |
539604| `subtreeCommits t` | every commit of the tree `t` (anything with `root` and `children`) in preorder, in time linear in its size: the value of `t.root :: (concat (map subtreeCommits t.children) or [])`. So it crashes only if `t` has no `root`, and a node whose `children` is not a list of values that have a `root` contributes only its own. The reference `commits` is this builtin (§9) |
540605| `diff a b` | a unified diff from the text of `a` to the text of `b`, split into lines after each `\n` (not at a lone `\r`), no `---`/`+++` header lines, three lines of context, each hunk headed `@@ -s,n +s,n @@` with the first line and the number of lines it shows of each side, as `diff -u` writes it, a line that has no trailing newline followed by `\ No newline at end of file`; `""` if equal; crashes if either is not UTF-8 |
541−| `difft p a b` | the output of difftastic comparing `a` to `b` as the file `p`; §7.10. Crashes if `difft` is not on `PATH` |
606+| `difft p a b` | the output of difftastic comparing `a` to `b` as the file `p`, with a unified-diff fallback when the executable is absent; §7.10 |
607+| `unrecorded repo` | paths changed between the invocation's loaded and snapshotted focus files; §7.4. Returns `[]` without snapshot context, including in a push expression |
542608| `treeWith o r` | the history rendered as a tree with options record `o`; §7.11 |
543609| `extract S v` | every subvalue of `v` that is an `S`; §4.12 |
544610| `meta i` | `{ hash, author, email, time }` for the visible commit with change id `i`: its current git hash, author name and email, committer time. Crashes for an id with no stored commit (one minted in this program) |
545−| `validate repo` | `repo` unchanged if persisting it would succeed; otherwise the crash persistence would raise (§7.5 steps 1–3 and 6). Persistence runs the same checks |
611+| `validate repo` | `repo` unchanged if persisting it would succeed; otherwise the crash persistence would raise (§7.5 steps 1–3, the root invariant of 4, and 6). A changed repository also checks the backend's author identity (§7.9). Persistence runs the same checks |
546612
547613### 4.10 Ids: existing and new
548614
@@ -631,6 +697,11 @@ signature is a crash, not a reason to try the other side. A signature on
631697a definition that is not a function (`user`, `conflicts`) is checked once,
632698against the value, when the definition is evaluated at load.
633699
700+Infix `&&` and `||` must be declared like other builtins. Their contracts
701+check each operand that is evaluated and the result, including a result
702+decided by the left operand alone. A skipped right operand is neither
703+evaluated nor checked.
704+
634705The check is one level deep and never inspects inside a list or a record's
635706field values:
636707
@@ -674,7 +745,9 @@ indented under their key or bullet. Colour follows §7.11's rules and is used
674745only when stdout is a terminal; nothing is conveyed by colour alone.
675746
676747Shapes are recognised by their exact field set, as in §4.12: `Commit`, `Entry`,
677−`Subtree`, `Repo`, `Frame`, `Change`. Any other record is generic.
748+`Subtree`, `Repo`, `Frame`, `Change`, checked in that order using their
749+configured definitions. Additional type aliases do not displace these
750+display forms. Any other record is generic.
678751
679752| kind | line form | block form |
680753|---|---|---|
@@ -699,6 +772,21 @@ Shapes are recognised by their exact field set, as in §4.12: `Commit`, `Entry`,
699772| list of other scalars | `a, b, c` | same, wrapped at the terminal width |
700773| any other list | `n items` | one block per item, separated by a blank line |
701774
775+Column tables use copyable shortest unique `@` prefixes for Id cells and
776+show the actual contents of Text-list cells (such as bookmark names), not
777+item counts. Empty Text-list cells are blank. On a terminal, columns are
778+fitted to the available width; if minimum column widths cannot fit, rows
779+are shown vertically with wrapped values. Piped tables retain untruncated
780+cell contents. These presentation choices do not change `show` or values.
781+
782+The reference status record includes `unrecorded : [Path]` alongside
783+`changed : [Path]`. The display labels `changed` as `in commit` to distinguish
784+changes against the commit's parent from file edits since the last recording.
785+Its empty fields read `No changes`, `No conflicts`, `No labels`,
786+`No description`, and `All edits recorded` for empty `unrecorded`.
787+Nonempty paths and labels remain visible. Presentation does not rename the
788+underlying `changed` field. Older five-field status records remain displayable.
789+
702790An empty list of any kind prints `none` (dim) in block form and `0 …` in
703791line form. Author and age in a `Commit` block are read from jj's commit
704792metadata by id;
@@ -722,12 +810,13 @@ Examples:
722810
723811```
724812$ j status
725−changed src/lexer.rs
726− src/parser.rs
727−conflicts src/lexer.rs
728−id wqzt·kpqxmnrvyxskptlmzzzzabcd
729−labels feature
730−message wip
813+in commit src/lexer.rs
814+ src/parser.rs
815+conflicts src/lexer.rs
816+id wqzt·kpqxmnrvyxskptlmzzzzabcd
817+labels feature
818+message wip
819+unrecorded All edits recorded
731820
732821$ j conflicts
733822⊗ wqzt wip feature
@@ -770,14 +859,19 @@ j: crash: expected one revision, got 0
770859
771860### 5.2 `show`
772861
773−`show v` renders `v` as an expression in the language, such that for every
774−value except closures, unresolved blobs, and values nested past the parser's
862+When it succeeds, `show v` renders `v` as an expression in the language, such
863+that for every value except closures, unresolved blobs, and values nested past the parser's
775864bound (§3.4: a list 40,000 deep, a record about 20,000), `show v` parses and
776865evaluates to a value equal to `v` under the same `config.j`. It never uses
777866colour and never truncates. A value holding a part the store cannot supply (a
778867`files` list or a file's content, §7.2) crashes `show` as it crashes any other
779868use: `show` writes no stand-in for the part.
780869
870+Binary blobs and non-regular file kinds require the declaration
871+`blobHex : Text -> Text -> Blob` in `config.j`; if it is missing, `show`
872+crashes with instructions to add it. Existing custom configs are not
873+modified automatically.
874+
781875| kind | rendering |
782876|---|---|
783877| `Int` | decimal |
@@ -786,7 +880,8 @@ use: `show` writes no stand-in for the part.
786880| list | `[v1 v2 …]` |
787881| record | `{ a = v, b = w }`, fields in ascending lexicographic order |
788882| `Id` | `@` followed by the full id, e.g. `@wqztkpqxmnrvyxskptlmzzzzabcd` |
789−| resolved `Blob` | `blob "…"` |
883+| resolved regular `Blob` with valid UTF-8 content | `blob "…"` |
884+| other resolved `Blob` | `blobHex "kind" "…"`, preserving file kind and bytes, with lowercase hexadecimal content |
790885| unresolved `Blob` | `{- unresolved -} blob "…"` with the conflict-marker rendering |
791886| builtin | its declared name, operators parenthesised: `map`, `(++)` |
792887| selector | the selector itself: `.id` |
@@ -824,12 +919,23 @@ Since `show` returns a `Text` and a top-level `Text` displays raw,
824919### 6.1 Location and loading
825920
826921`j` reads `$XDG_CONFIG_HOME/j/config.j`, defaulting to `~/.config/j/config.j`.
827−Expression evaluation, `push`, and `fetch` load the configuration; `init` and
922+Expression evaluation and `push` load the configuration; `init` and
828923`clone` need `user`. When there is no repository yet (`init`, `clone`), only
829924`user` is evaluated; `Id` literals are not resolved and no other definition
830−is evaluated. `remote`, `undo`, `redo`, and `ops` do not load configuration.
925+is evaluated. `remote`, `fetch`, `undo`, `redo`, and `ops` do not load configuration.
831926When a command needs configuration and none exists, the binary writes its
832−embedded reference `config.j` (§9) to that path as an editable starting point.
927+embedded reference `config.j` (§9) to that path as an editable starting point,
928+readable and writable by its owner, and reports its path. An existing file is
929+never overwritten, including when it cannot be read or contains invalid
930+UTF-8; such errors exit 3.
931+Configuration diagnostics identify the actual file; parse errors include the
932+line number and a bounded source excerpt, with a pointer to `j help config`.
933+Excerpts escape terminal controls and are capped at 160 display columns and
934+characters, with an ellipsis when truncated. Expression parse errors, including
935+push expressions, likewise identify their source line. Runtime diagnostics may
936+add typo suggestions from the actual vocabulary, quoting examples for Text
937+arguments, or missing-argument guidance derived from the command signature;
938+they never retry an expression or change its meaning or exit code.
833939An existing user configuration is not updated when the binary changes. The
834940reference file is also distributed at `<prefix>/share/j/config.j`.
835941
@@ -839,6 +945,23 @@ names read it without evaluation. If it is missing they use the embedded
839945reference in memory without writing a file. The empty-invocation guide does
840946not read or write configuration either.
841947
948+### 6.1.1 Explicit setup
949+
950+`setup` creates the full embedded configuration at the configured path without
951+opening a repository. With no arguments, it creates an editable default and
952+reports the path and instructions to set `user`. With `NAME EMAIL`, it checks
953+the identity rules of §6.2 and writes those values into the complete reference
954+configuration, serialized as language text literals. Names containing spaces
955+are supplied as a separately quoted shell argument.
956+
957+Setup never overwrites an existing file. Without identity arguments, an
958+existing file reports its effective identity when valid, or guidance to
959+edit it otherwise. With identity arguments, an
960+existing file is a usage error (exit 2), with instructions to edit manually.
961+Argument counts other than zero or two are usage errors. Creating the file
962+also creates missing parent directories; exclusive creation preserves a file
963+created concurrently by another invocation. No repository state is changed.
964+
842965### 6.2 Validation
843966
844967Loading fails (exit 3) if any of these hold:
@@ -862,6 +985,13 @@ not declared is simply unbound, and using it is a runtime crash.
862985
863986Definitions may reference each other in any order.
864987
988+Before authoring commits (`init`, `clone`, or an expression that persists a
989+change), the effective identity must also have nonblank name/email and must
990+not retain the shipped `Your Name` or `you@example.com` placeholders. Such
991+identity failures are configuration errors (exit 3) and do not persist the
992+edit. Inspection and unchanged expressions remain usable with placeholder
993+identity, so users can inspect and repair existing repositories.
994+
865995### 6.3 Definitions the interpreter reads
866996
867997Four names in `config.j` are read by the interpreter itself, not only by
@@ -890,6 +1020,10 @@ but nothing here requires the `jj` binary to be installed.
8901020Walk up from the current directory to the first directory containing `.jj`.
8911021Only the default workspace is supported; if the working directory belongs to
8921022another workspace, exit 2.
1023+The working copy must also include the whole repository: sparse patterns
1024+must contain the root path. Otherwise exit 2 without changing the patterns,
1025+files, or operations, with instructions to run
1026+`jj sparse set --clear --add .` before using `j`.
8931027
8941028### 7.2 Building the `Repo` value
8951029
@@ -993,10 +1127,11 @@ some other value in its place.
9931127 `./a` to `./a/b` replayed onto `./a/b`; the deletion of `./a/z` replayed
9941128 onto a snapshot where `./a` is a file gives that file), and it may crash
9951129 where jj carries a conflict that one of the three held at `./a`.
996−- `unresolved`, `blob`, and `text` are as in §4.9. `blob` produces a regular
997− file. File type (executable, symlink) travels with a `Blob` but cannot be
998− changed in the language. `text` of a symlink is its target; `text` of a
999− non-UTF-8 blob crashes.
1130+- `unresolved`, `blob`, `blobHex`, and `text` are as in §4.9. `blob` produces
1131+ a regular file from text. `blobHex` constructs a regular file, executable,
1132+ or symlink from its kind and hexadecimal bytes, so `show` can preserve both
1133+ content and file type. `text` of a symlink is its target; `text` of a
1134+ non-UTF-8 blob crashes. Persisting a symlink with a non-UTF-8 target crashes.
10001135
10011136### 7.4 Working-directory invariant
10021137
@@ -1092,6 +1227,14 @@ directory.
10921227
10931228Reserved commands never snapshot.
10941229
1230+The `unrecorded : Repo -> [Path]` builtin compares the invocation's loaded
1231+focus files with its snapshotted focus files, using the same path/content
1232+comparison as `changed`. The result describes the original working-copy
1233+edits, independently of changes an expression makes to its `Repo` argument.
1234+It returns `[]` when either snapshot context is absent, including reserved
1235+push evaluation. Read-only status and preview calls do not record these paths;
1236+`save = id` persists their snapshot through the normal expression path.
1237+
10951238### 7.5 Persistence
10961239
10971240Given the loaded value `old` and the result `new`, one jj operation is
@@ -1099,11 +1242,14 @@ recorded, described by the expression text (truncated to 200 characters).
10991242Within it:
11001243
110112441. **Validate shape.** `new` has the shape of a `Repo`; every `id` occurs
1102− exactly once; every snapshot has unique paths, none of them the root `./`
1103− and none both a file and a directory (§7.3); every path component of the
1104− focus, of every other commit that is not in `old`, and of every path any
1105− other commit holds that it did not hold in `old` (§7.3), is a name a
1106− checkout can create: not empty, `.`, `..`, `.git` or `.jj`, without `/`
1245+ exactly once; every commit's `message` is a `Text`; every snapshot entry
1246+ has a `path` list of texts and a `content` that is a `Blob`; every snapshot
1247+ has unique paths, none of them the root `./` and none both a file and a
1248+ directory (§7.3); every path
1249+ component of the focus, of every other commit that is not in `old`, and
1250+ of every path any other commit holds that it did not hold in `old`
1251+ (§7.3), is a name a checkout can create: not empty, `.`, `..`, `.git` or
1252+ `.jj`, without `/`
11071253 or NUL, and a name the workspace's filesystem can hold. A name the working
11081254 directory holds (§7.4) is one. A name longer than 255 bytes, the limit of
11091255 ext4, is tried on the filesystem, since exFAT, NTFS and APFS count 255
@@ -1228,10 +1374,18 @@ language: `fetch` and `push` are the only things that change them.
12281374 creating it if absent;
12291375 - `{ delete : Text }` — delete the bookmark of that name.
12301376
1377+ Before sending a valid push, inspect the working directory without saving
1378+ a snapshot or scan state. If it has unrecorded edits, print a warning to
1379+ stderr (also when piped or colors are disabled) explaining that push sends
1380+ recorded content only, and that `j id` followed by another push includes
1381+ those edits. The warning does not change the requested push's targets or
1382+ prevent deliberate publication of recorded commits.
1383+
12311384 Duplicates are merged; two records with the same `name` and different
12321385 effects are a crash. It is refused (exit 1, nothing pushed) if:
1233− - a `name` is not a valid git branch name, an `id` is not a visible commit,
1234− or a delete names a bookmark the remote does not have;
1386+ - a `name` is not a valid git branch name, an `id` is not a visible commit
1387+ or names the synthetic root (which has no Git commit), or a delete names
1388+ a bookmark the remote does not have;
12351389 - any commit that would be sent has an unresolved file or an empty
12361390 description (the commits that would be sent are the records' `id`s and
12371391 their ancestors that no bookmark on `origin` reaches as of the last
@@ -1270,47 +1424,68 @@ language: `fetch` and `push` are the only things that change them.
12701424 bookmarks, and the refused push does not touch the label. After a
12711425 successful push the labels reflect the new positions. It records one
12721426 operation, even when it sends two git pushes. An empty list sends nothing
1273− to the remote.
1427+ to the remote, records no operation, and always reports that no updates
1428+ were selected, with guidance for selecting a first bookmark. A nonempty
1429+ successful push reports published, deleted, and unchanged bookmark counts
1430+ on an interactive terminal, including an entirely unchanged selection.
12741431 If the remote accepts some of the updates and rejects others, that
12751432 operation records the accepted ones, whose labels move, and `push` exits 1
12761433 naming the rejected bookmarks and any deletes it did not send.
12771434
12781435Only the remote named `origin` is supported in this version; `fetch` and
1279−`push` exit 1 with `j: no remote origin` when it is not configured.
1436+nonempty `push` exit 1 with `j: no remote origin` when it is not configured.
12801437Authentication is git's: the ssh agent for ssh URLs, git credential helpers
12811438for https, as jj-lib's git backend provides them; `j` adds no prompting.
12821439Bringing work up to date is a separate expression, e.g. `rebase trunk`.
12831440
12841441### 7.7 Operations, undo, redo, and the lock
12851442
1286−Every persisting `j` program, and each of `fetch` and `push`, records exactly
1287−one jj operation, described by the expression or command text. Crashes record
1288−nothing, except as §7.5 step 7 says.
1289−
1290−**Kinds.** `j` marks each operation it records, in the operation's metadata,
1291−as *normal*, *undo* (naming the operation it undid), or *redo* (naming the
1292−undo it reversed). Operations recorded by other tools are treated as normal,
1293−except that an operation whose description carries jj's own undo or redo
1294−marker is treated accordingly, so mixed use chains correctly where the marker
1295−is present and degrades to "undo the newest operation" where it is not.
1296−
1297−**`undo`** finds the operation to undo: starting at the head, while the
1298−current operation is an undo, move to the operation it undid and then to that
1299−operation's parent; while it is a redo, move past the operation it reapplied.
1300−The first normal operation reached is undone by recording a new operation
1301−that restores its parent's view, marked as an undo of it. If none is found,
1302−or the one found is the operation `init` or `clone` recorded (§7.8), before
1303−which there was no repository, exit 1 with `j: nothing to undo`.
1304−
1305−**`redo`** requires the head to be an undo (possibly reached through redos);
1306−it records an operation restoring the view of the operation that undo
1307−discarded, marked as a redo. Otherwise exit 1 with `j: nothing to redo`.
1443+Every persisting `j` program, each `fetch`, and each nonempty `push` records
1444+exactly one jj operation, described by the expression or command text. A push's
1445+description includes its `push ` prefix, so an expression named `init` or
1446+`clone` cannot be mistaken for the operation that created the repository.
1447+Crashes record nothing, except as §7.5 step 7 says.
1448+
1449+**Kinds.** An operation is *normal*, *undo*, or *redo*, determined by its
1450+description. New undo and redo records use jj's current markers,
1451+`undo: restore to operation <id>` and `redo: restore to operation <id>`.
1452+The id names the exact operation whose view was restored, retaining the
1453+navigation point for later undo and redo commands.
1454+Older `j` markers (`undo: <id>` naming the discarded operation and
1455+`redo: <id>` naming the reversed undo) and jj's legacy `undo operation <id>` /
1456+`redo operation <id>` markers remain readable with their original meanings.
1457+An operation without a recognized marker is normal, so mixed use degrades
1458+to "undo the newest operation" where no marker is present.
1459+
1460+For current markers, start at the head and follow restored-operation links
1461+while their kind matches the command: undo links for `undo`, redo links for
1462+`redo`. The command then selects that operation's parent. Follow links of
1463+the same kind at the selected target too, and record the final target's id
1464+and view. Reversing direction therefore restores the opposite marker's
1465+parent, and either CLI can continue the other's undo/redo chain.
1466+
1467+**`undo`** restores the state before the operation reached by that walk.
1468+If no parent exists, or the operation reached is the `init` or `clone`
1469+operation (§7.8), before which there was no repository, exit 1 with
1470+`j: nothing to undo`.
1471+
1472+**`redo`** requires the operation reached through redo links to be an undo;
1473+it restores the state that undo discarded. Otherwise exit 1 with
1474+`j: nothing to redo`.
1475+
1476+For older `j` markers, undo follows an undo's discarded operation to its
1477+parent, or a redo's reversed undo to the operation it discarded. Redo
1478+follows a redo's reversed undo to its parent until it reaches an undo,
1479+then restores that undo's parent. This preserves pending redos when a
1480+history crosses from older markers into current ones.
13081481
13091482Both refuse, before doing anything, if the working directory holds a change
13101483the focused commit does not, as a snapshot would find it (§7.4; a stale
13111484working copy holds none where the directory holds what was last checked out
1312−or what the commit has): `j: working copy has changes not in @; run \`j id\`
1313−to record them or discard them`. Both record their operation and then check
1485+or what the commit has). The refusal explains that `j id` preserves those
1486+edits in a new operation, and the next undo reverses that recording before
1487+reaching the earlier operation; users should inspect `j ops` and can recover
1488+the recording with redo. Both record their operation and then check
13141489out the restored focus, as persistence does (§7.5 step 7): a checkout that
13151490fails leaves the operation recorded, and `redo` or `undo` goes back.
13161491Undoing a `push` restores the recorded labels and leaves the remote untouched.
@@ -1363,7 +1538,9 @@ that state, §7.2 applies.
13631538 it: the last component of `URL`'s path, a final `.git` component skipped,
13641539 minus `.git` (`repo` for `/srv/repo/.git`, `host:repo.git` and
13651540 `https://host/team/repo`); a `URL` that leaves no name, such as `/` or
1366− `..`, is a usage error asking for `DIR`. It requires `user`.
1541+ `..`, is a usage error asking for `DIR`. It requires `user`. Interactive
1542+ confirmation includes a shell-quoted command to enter the clone directory
1543+ and run `j status`; cloning cannot change the caller's working directory.
13671544- **`remote URL`** sets the URL of `origin`, creating the remote if it does not
13681545 exist. It does not fetch.
13691546
@@ -1394,16 +1571,27 @@ Commits are authored and committed as `user.name <user.email>` from
13941571`config.j` (§6.3). `j` reads no other configuration; jj-lib's settings object
13951572is constructed by `j` from `user` and fixed defaults. `init`, `clone`, and
13961573persistence exit 3 if `user` is missing or malformed, since that is a
1397−configuration error.
1574+configuration error. Authoring also refuses blank fields or the shipped
1575+`Your Name` / `you@example.com` placeholders (§6.2), with instructions to
1576+edit the actual config file. Read-only expressions and unchanged results
1577+remain available with placeholder identity. Successful interactive setup
1578+reports the configured identity; terminal control characters are escaped
1579+in confirmations without modifying the identity stored in commits.
1580+`validate` checks the author identity when its argument differs from the
1581+loaded repository, so validated previews report the same identity refusal
1582+before saving. That refusal is a catchable crash, like other `validate`
1583+failures. An unchanged `validate` and previews without `validate` remain usable.
13981584
13991585### 7.10 External diff rendering
14001586
14011587`difft p a b` runs difftastic. It is the only builtin that executes another
14021588program.
14031589
1404−1. Write the content of `a` and `b` to two temporary files whose names end in
1405− the last component of `p` (so difftastic's language detection sees the
1406− real filename), inside a fresh temporary directory.
1590+1. Write the content of `a` and `b` to files in separate `old` and `new`
1591+ subdirectories of a fresh temporary directory. Each file keeps the last
1592+ component of `p` unchanged, so valid names stay within the filesystem's
1593+ length limit and difftastic sees the real filename. A last component
1594+ that is not a filename is a crash; an empty path uses `file`.
140715952. Run `difft OLD NEW` with the process's stdin closed and its environment
14081596 inherited unchanged, except: if `DFT_COLOR` is unset and `j`'s stdout is a
14091597 terminal, set `DFT_COLOR=always`; if `DFT_WIDTH` is unset and `j`'s stdout
@@ -1413,15 +1601,24 @@ program.
141316014. Return stdout as `Text`. A non-zero exit status is not an error (difftastic
14141602 uses it to signal "differences found" under some options), so what a
14151603 `difft` that fails printed, even nothing, is the result; a failure to
1416− execute the program is a crash naming it.
1604+ execute the program because it is absent uses the fallback below. Other
1605+ launch failures are crashes naming the tool and the OS error.
1606+
1607+If the executable is absent (`NotFound`), return a unified diff with three
1608+lines of context and `--- a/PATH` / `+++ b/PATH` headers using the complete
1609+path `p`. Paths containing whitespace or control characters are quoted.
1610+Content that is not UTF-8 or contains NUL receives a binary-difference summary.
1611+Equal bytes produce empty output. This fallback is usable without installing
1612+another program or changing configuration.
14171613
14181614All rendering options are difftastic's own, set through its `DFT_*`
14191615environment variables. `j` adds no options of its own. `diff` remains
1420−available as a dependency-free unified diff; the reference `config.j` uses
1421−`difft` for display, so a user without difftastic edits one line.
1616+available as a dependency-free unified diff without path headers; the
1617+reference `config.j` uses `difft` for display and receives the automatic
1618+fallback when difftastic is absent.
14221619
1423−Packaging: difftastic is a runtime dependency; on Nix, wrap `j` with `difft`
1424−on its `PATH`.
1620+Packaging: difftastic is an optional runtime enhancement; on Nix, wrap `j`
1621+with `difft` on its `PATH`.
14251622
14261623### 7.11 Tree rendering
14271624
@@ -1445,7 +1642,8 @@ run — have no metadata: their margin cells are blank, their size bar is
14451642computed from the value, and they sort last among their siblings. The same
14461643holds for a `Commit` block in display (§5.1).
14471644
1448−Missing fields crash. `color = "auto"` means colour iff stdout is a terminal.
1645+Missing fields crash. `color = "auto"` means colour iff stdout is a terminal
1646+and `TERM` is not `dumb`.
14491647If the environment variable `NO_COLOR` is set and non-empty, colour is never
14501648used regardless of `color`. `config.j` fixes the defaults by defining `tree`.
14511649
@@ -1663,6 +1861,10 @@ Drop = { delete : Label } -- delete the remote's bookmark
16631861------------------------------------------------------------------------------
16641862
16651863(.) : (b -> c) -> (a -> b) -> a -> c -- composition, right to left
1864+-- Return a value unchanged. As a repository command, `j id` records the
1865+-- snapshotted working-copy edits in the focused commit. `j save` is the
1866+-- descriptive alias in this reference config; inspections such as `j status`
1867+-- and `j tree` do not record those edits.
16661868id : a -> a
16671869const : a -> b -> a
16681870crash : Text -> a -- abort the program with a message
@@ -1730,21 +1932,33 @@ splitOn : Text -> Text -> [Text] -- splitOn "/" "a/b" = ["a" "b"]
17301932-- file ./a would, crashes, naming the path (§7.3).
17311933replay : Snapshot -> Change -> Snapshot
17321934unresolved : Blob -> Bool
1935+
1936+-- Paths edited in the working copy since the last recorded repository state.
1937+-- `j status` includes this list; `j save` records it in the focused commit.
1938+-- This measures the working copy, even when composed after an in-memory edit.
1939+unrecorded : Repo -> [Path]
1940+touchedPaths : Change -> [Path] -- paths whose content differs, one pass;
1941+ -- blobs compared by content id, bytes never read
17331942blob : Text -> Blob -- a resolved regular file
1943+blobHex : Text -> Text -> Blob -- kind (regular/executable/symlink), hex bytes
17341944text : Blob -> Text -- its content (markers if unresolved)
17351945by : Id -> Repo -> Repo -- focus the commit with this id; crash if absent
1946+lookupCommit : Id -> Repo -> Commit -- indexed commit lookup without moving the focus
1947+lookupContent : Path -> Snapshot -> Blob -- indexed file content; an empty blob if absent
17361948subtreeCommits : a -> [Commit] -- every commit of a tree in preorder, in one
17371949 -- pass: what `commits` (below) is
17381950meta : Id -> Meta -- git hash, author, email, committer time;
17391951 -- crash for a commit not yet persisted
17401952validate : Repo -> Repo -- the repo unchanged, or the crash persisting
1741− -- it would produce (shape, labels, immutability,
1953+ -- it would produce (shape, root, labels, immutability,
17421954 -- focus). `tree . validate . rebase trunk` is an
17431955 -- honest dry run.
17441956diff : Blob -> Blob -> Text -- unified diff from the first to the second
1745−difft : Path -> Blob -> Blob -> Text -- difftastic's rendering; needs difft on PATH.
1746− -- Options via DFT_* environment variables.
1747−treeWith : { detail : Int, margin : Bool, elide : Bool, icons : Bool, color : Text }
1957+difft : Path -> Blob -> Blob -> Text -- difftastic when difft is on PATH; otherwise
1958+ -- unified diff with path headers and 3 context lines,
1959+ -- or a binary-files summary. DFT_* configures difft.
1960+treeWith : { detail : Int, margin : Bool, elide : Bool, icons : Bool, color : Text
1961+ , lanes : Int, author : Bool, date : Bool, files : Bool }
17481962 -> Repo -> Text -- the history as a tree; see the spec
17491963
17501964-- Every subvalue of a given shape, however deeply nested. A shape is a type
@@ -1830,6 +2044,9 @@ restrict = \m -> filter (\e -> m e.path)
18302044select : Fileset -> Snapshot -> Snapshot -> Snapshot -- m from a, the rest from b
18312045select = \m a b -> restrict m a ++ restrict (neg m) b
18322046
2047+-- Unresolved paths in a snapshot. For the focus, inspect `j status` or
2048+-- `j conflicted . files`, edit the listed files to remove conflict markers,
2049+-- then run `j save` and inspect `j status` again. See `j help conflicts`.
18332050conflicted : Snapshot -> [Path]
18342051conflicted = \s -> map (.path) (filter (\e -> unresolved e.content) s)
18352052
@@ -1914,6 +2131,11 @@ minus = \a b repo -> let ys = b repo in filter (\i -> not (member i ys)) (a repo
19142131matching : (Commit -> Bool) -> Revset -> Revset
19152132matching = \p rs repo -> filter (\i -> p (commitAt i repo)) (rs repo)
19162133
2134+-- Find every commit with unresolved files. `j conflicts` lists candidates;
2135+-- use `j by @ID` to focus a mutable one, then `j status` to find its paths.
2136+-- Edit each conflicted file, remove the conflict markers, and run `j save`.
2137+-- Check `j status` again; `j clean` means the focus has no unresolved files,
2138+-- not that the working copy has no unrecorded edits. `j review` shows the diff.
19172139conflicts : Revset
19182140conflicts = matching (\c -> not (null (conflicted c.files))) all
19192141
@@ -1922,7 +2144,10 @@ firstOf : [Revset] -> Revset
19222144firstOf = \rss repo -> foldl (\acc rs -> if null acc then rs repo else acc) [] rss
19232145
19242146-- The main line: whichever of these names the remote has, first one wins.
1925−-- Empty in a repository with no remote, in which case nothing is immutable.
2147+-- Empty when no fetched remote label is named main, master, or trunk;
2148+-- nothing is then immutable. Configure `j remote URL`, run `j fetch`, and
2149+-- inspect `j tree` to see remote labels. A remote with another default label
2150+-- needs this definition adjusted. Start work with j 'new . goto trunk'.
19262151trunk : Revset
19272152trunk = firstOf [%main %master %trunk]
19282153
@@ -1938,21 +2163,33 @@ immutable = ancestorsOf trunk
19382163-- Focusing. `goto` is the only place a revset must have exactly one member.
19392164------------------------------------------------------------------------------
19402165
2166+-- Move to the single commit selected by a revset.
2167+-- Example: j 'goto %feature'; use j 'new . goto trunk' to start on the main line.
19412168goto : Revset -> Edit
19422169goto = \rs repo ->
19432170 let is = rs repo
2171+ candidate = \i ->
2172+ let c = commitAt i repo
2173+ message = head (splitOn "\n" c.message)
2174+ in " " ++ show i ++ " " ++ (if message == "" then "(no description)" else message) ++ "\n"
19442175 in if length is == 1
19452176 then by (head is) repo
1946− else crash ("expected one revision, got " ++ show (length is))
2177+ else if null is
2178+ then crash "expected one revision, got 0; no matching revision. Run `j tree` to inspect available revisions. For a missing remote label such as %main or trunk, configure `j remote URL`, run `j fetch`, and check the label names in `j tree`."
2179+ else crash ("expected one revision, got " ++ show (length is) ++ "\nCandidates:\n" ++ concat (map candidate (take 8 is)) ++ (if length is > 8 then " More candidates: run `j tree`.\n" else "") ++ "Choose a mutable commit with `j by @ID`, or start a child with `j 'new . by @ID'`; replace @ID with a candidate above.")
19472180
19482181commitAt : Id -> Repo -> Commit
1949−commitAt = \i repo -> (by i repo).root
2182+commitAt = lookupCommit
19502183
19512184prev : Edit -- the parent
19522185prev = goto parents
19532186
1954−next : Edit -- the only child
1955−next = goto kids
2187+-- Move to the only child. At a tip, `j new` starts a child; when there are
2188+-- several children, choose one of the candidates with `j by @ID`.
2189+next : Edit
2190+next = \repo -> if null (kids repo)
2191+ then crash "next: this commit has no children; run `j new` to start a new child, or `j tree` to inspect the history"
2192+ else goto kids repo
19562193
19572194tip : Edit -- end of the line of only-children
19582195tip = \repo -> tip (next repo) or repo
@@ -1978,6 +2215,8 @@ eachChild = forEach kids
19782215guard : (Repo -> Bool) -> Edit -- crash unless p holds
19792216guard = \p repo -> if p repo then repo else crash "guard"
19802217
2218+-- Whether the focused commit has no unresolved files. This says nothing
2219+-- about unrecorded working-copy edits; inspect those with `j unrecorded`.
19812220clean : Repo -> Bool
19822221clean = \repo -> null (conflicted repo.root.files)
19832222
@@ -2016,11 +2255,28 @@ rewrite = \f repo ->
20162255-- Commands
20172256------------------------------------------------------------------------------
20182257
2258+-- Record the current working-copy edits in the focused commit.
2259+-- Example: j save; use j status first to inspect unrecorded paths.
2260+-- This is id restricted to a Repo: the final Repo result triggers recording.
2261+-- It does not publish anything. `j new` starts a child; `j id` records too.
2262+save : Edit
2263+save = id
2264+
2265+-- Preview an edit and validate that its result could be recorded, then show
2266+-- the resulting tree. This returns Text, so it records nothing.
2267+-- Examples: j 'preview squash'; j 'preview (rebase trunk)'.
2268+-- This helper is in the reference config. Existing custom configs are not
2269+-- upgraded automatically; add it there or use `tree . validate . EDIT`.
2270+preview : Edit -> Repo -> Text
2271+preview = \edit -> tree . validate . edit
2272+
20192273-- Set the message of the focused commit.
2274+-- Example: j 'describe "fix login"'
20202275describe : Text -> Edit
20212276describe = \m -> mapRoot (\c -> c { message = m })
20222277
20232278-- Start an empty child of the focus and move to it.
2279+-- Examples: j new; j 'describe "next change" . new'
20242280new : Edit
20252281new = \repo -> addChild (newCommit repo.root.files) repo
20262282
@@ -2028,6 +2284,7 @@ new = \repo -> addChild (newCommit repo.root.files) repo
20282284-- new empty commit on that parent becomes the focus (the parent may be
20292285-- immutable, and the focus never is). Refused for a commit the remote has a
20302286-- name for: push something else under that name first, or forget the name.
2287+-- Example: j abandon; validate a preview first with j 'preview abandon'.
20312288abandon : Edit
20322289abandon = \repo ->
20332290 let c = repo.root
@@ -2037,17 +2294,20 @@ abandon = \repo ->
20372294 else new (remove (eachChild (replayOnto p) repo))
20382295
20392296-- Push the files matching m from the focus down into its parent.
2297+-- Example: j 'contract (ext "rs")'
20402298contract : Fileset -> Edit
20412299contract = \m repo -> let a = repo.root
20422300 in at parents (rewrite (\q -> q { files = select m a.files q.files })) repo
20432301
20442302-- Fold the focus entirely into its parent; a new empty commit becomes the focus.
2303+-- Example: j squash; preview first with j 'preview squash'.
20452304squash : Edit
20462305squash = abandon . contract everything
20472306
20482307-- Push the files matching m from the focus up into a new child, which becomes
20492308-- the focus and inherits the old focus's children. Labels stay with the
20502309-- original commit, since they are the remote's.
2310+-- Example: j 'split (ext "rs")'; use (under ./src) to select a directory.
20512311split : Fileset -> Edit
20522312split = \m repo ->
20532313 let a = repo.root
@@ -2057,6 +2317,7 @@ split = \m repo ->
20572317 in by x.id (repo { root = a', children = [{ root = x, children = repo.children }] })
20582318
20592319-- Move the focused subtree onto another commit. Returns to the moved commit.
2320+-- Example: j 'rebase trunk'; preview with j 'preview (rebase trunk)'.
20602321rebase : Revset -> Edit
20612322rebase = \rs repo ->
20622323 let d = (goto rs repo).root
@@ -2067,6 +2328,7 @@ rebase = \rs repo ->
20672328 in by me (attach x.subtree (by d.id x.rest))
20682329
20692330-- Apply the focused commit's change onto another commit, as a new child there.
2331+-- Example: j 'pick %main'
20702332pick : Revset -> Edit
20712333pick = \rs repo ->
20722334 let ch = changeOf repo
@@ -2074,6 +2336,7 @@ pick = \rs repo ->
20742336 in at rs (addChild (newCommit (replay t.files ch))) repo
20752337
20762338-- A new child of the focus that undoes the change of the given commit.
2339+-- Example: j 'backout %feature'
20772340backout : Revset -> Edit
20782341backout = \rs repo ->
20792342 let ch = changeOf (goto rs repo)
@@ -2119,20 +2382,22 @@ entryAt = \p s -> filter (\e -> e.path == p) s
21192382
21202383-- The content at path p, or an empty file if absent.
21212384contentAt : Path -> Snapshot -> Blob
2122−contentAt = \p s -> (head (entryAt p s)).content or blob ""
2385+contentAt = lookupContent
21232386
21242387-- The paths a change touches.
21252388touched : Change -> [Path]
2126−touched = \ch ->
2127− let paths = map (.path) ch.to ++ filter (\p -> not (member p (map (.path) ch.to))) (map (.path) ch.from)
2128− in filter (\p -> entryAt p ch.to /= entryAt p ch.from) paths
2389+touched = touchedPaths
21292390
2130−-- Paths whose content differs between the focus and its parent.
2391+-- Paths changed by the focused commit compared with its parent, including
2392+-- the working-copy snapshot. These can remain nonempty after `j save`;
2393+-- `j unrecorded` shows only working-copy edits not yet recorded.
2394+-- Example: j changed
21312395changed : Repo -> [Path]
21322396changed = \repo -> touched (changeOf repo)
21332397
2134−-- What the focus changes, rendered by difftastic, one record per changed path.
2135−-- Replace difft with (\p -> diff) to use the built-in unified diff instead.
2398+-- What the focus changes, one record per changed path. Uses difftastic when
2399+-- installed; a missing difft falls back to a unified diff with path headers
2400+-- and 3 context lines, or a binary-files summary.
21362401diffs : Repo -> [{ path : Path, diff : Text }]
21372402diffs = \repo ->
21382403 let ch = changeOf repo
@@ -2140,23 +2405,30 @@ diffs = \repo ->
21402405
21412406-- The same, as one page of text. A Text result prints raw, so `j review` is
21422407-- readable in the terminal. A focus that changes nothing reads "no changes";
2143−-- a difft that cannot be run (not on PATH) is a crash saying so, and
2144−-- otherwise whatever difft prints, even nothing, is the page.
2408+-- missing difft uses the built-in unified diff (path headers, 3 context lines)
2409+-- or a binary-files summary. Other execution errors remain visible; an
2410+-- installed difft's output, even nothing, is the page.
2411+-- Example: j review; difftastic is optional.
21452412review : Repo -> Text
21462413review = \repo ->
21472414 let ds = diffs repo
21482415 in if null ds then "no changes\n" else concat (map (\d -> d.diff) ds)
21492416
2150−-- A summary of the focus.
2151−status : Repo -> { id : Id, message : Text, labels : [Label], changed : [Path], conflicts : [Path] }
2417+-- A summary of the focus. `changed` lists changes in the commit compared
2418+-- with its parent; `unrecorded` lists working-copy edits not yet recorded.
2419+-- Inspection does not record anything. Run `j save` to record those edits.
2420+-- Example: j status
2421+status : Repo -> { id : Id, message : Text, labels : [Label], changed : [Path], conflicts : [Path], unrecorded : [Path] }
21522422status = \repo ->
21532423 { id = repo.root.id
21542424 , message = repo.root.message
21552425 , labels = repo.root.labels
21562426 , changed = changed repo
2157− , conflicts = conflicted repo.root.files }
2427+ , conflicts = conflicted repo.root.files
2428+ , unrecorded = unrecorded repo }
21582429
21592430-- A summary of every commit, top first, in preorder.
2431+-- Example: j log
21602432log : Repo -> [{ id : Id, message : Text, labels : [Label], focus : Bool }]
21612433log = \repo ->
21622434 map (\c -> { id = c.id
@@ -2167,11 +2439,21 @@ log = \repo ->
21672439
21682440-- The history as a tree. Edit these records to change what `j tree` shows.
21692441-- (The parentheses are required: `f { … }` would be a record update of f.)
2442+-- The default `tree` shows every commit, sorted by time (parents always above
2443+-- their children); `treeCompact` folds uninteresting runs and far subtrees.
2444+-- Examples: j tree; j 'tree . squash' previews an edit without saving it.
21702445tree : Repo -> Text
2171−tree = treeWith ({ detail = 1, margin = false, elide = true, icons = false, color = "auto" })
2446+tree = treeWith ({ detail = 1, margin = false, elide = false, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
2447+
2448+treeCompact : Repo -> Text
2449+treeCompact = treeWith ({ detail = 1, margin = false, elide = true, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
21722450
21732451treeFull : Repo -> Text
2174−treeFull = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto" })
2452+treeFull = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
2453+
2454+-- The full tree plus the extra data columns: date, files changed, author.
2455+treeData : Repo -> Text
2456+treeData = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto", lanes = 4, author = true, date = true, files = true })
21752457```
21762458
21772459---
@@ -2179,9 +2461,9 @@ treeFull = treeWith ({ detail = 2, margin = true, elide = false, icons = false,
21792461## 10. Testing
21802462
21812463The interpreter must be testable without a jj repository. Provide an in-memory
2182−implementation of the domain builtins (`replay`, `unresolved`, `blob`,
2464+implementation of the domain builtins (`replay`, `unresolved`, `blob`, `blobHex`,
21832465`text`, `by`, `extract`, `meta`, `difft` as a stub, and the minting
2184−of `@`) in which a blob is text, a
2466+of `@`) in which a resolved blob carries bytes and a file kind, a
21852467conflict is a record of the three sides, and `replay` resolves a path exactly
21862468when at most one side changed it or both made the same change, path by path
21872469even where a file meets a directory, which jj merges as one value (§7.3).
@@ -2217,9 +2499,10 @@ here is out of scope (§12). Nothing here uses the `jj` binary.
22172499**Getting a repository**
22182500
22192501```
2220−$EDITOR ~/.config/j/config.j -- set `user` once
2502+j setup 'Your actual name' you@your-domain.example -- create the full config once
22212503j init -- new repository here, or adopt an existing .git
2222−j clone git@host:org/repo.git -- or an existing one
2504+j clone git@host:org/repo.git project -- or clone an existing one
2505+cd project -- after clone, enter its directory
22232506j remote git@host:org/repo.git -- attach a remote to an init'd repo
22242507```
22252508
@@ -2228,9 +2511,9 @@ j remote git@host:org/repo.git -- attach a remote to an init'd repo
22282511```
22292512j tree -- the history as a tree, focus marked
22302513j treeFull -- the same with sizes, ages, authors, changed paths
2231−j status -- id, message, labels, changed paths, conflicts of @
2514+j status -- focus, in-commit changes, unrecorded edits, conflicts
22322515j log -- every commit as a record
2233−j review -- difftastic rendering of @ against its parent
2516+j review -- difftastic or unified diff of @ against its parent
22342517j review . by @kpq -- the same for another commit, without moving
22352518j diffs -- the same, one record per path
22362519j here -- @'s id
@@ -2261,7 +2544,8 @@ j rebase trunk
22612544j guard clean . rebase trunk or id -- only if it comes out clean
22622545j pick @kpq
22632546j 'forEach (descendantsOf here) (describe "wip")'
2264−j id -- record the working directory, change nothing else
2547+j save -- record the working directory, change nothing else
2548+j 'preview squash' -- validate and show the result without saving
22652549j tree . squash -- dry run: show the result, persist nothing
22662550j tree . validate . squash -- the same, refusing what persistence would refuse
22672551j squash && j tree -- do, then look
@@ -2283,7 +2567,10 @@ j rebase trunk -- bring the stack up to date
22832567A rebase or fetch that conflicts leaves unresolved files in the affected
22842568commits. `j conflicts` lists them; `j by @…` moves there, which checks the
22852569files out with conflict markers; editing the files and running any persisting
2286−expression (`j id` at minimum) records the resolution.
2570+expression (`j save` or `j id` at minimum) records the resolution. Run
2571+`j status` to verify `No conflicts` and `All edits recorded` in the focus,
2572+and `j conflicts` to check the rest of the history. No continue command is
2573+needed.
22872574
22882575**Mistakes**
22892576
@@ -2298,7 +2585,7 @@ j redo
22982585Any definition added to `config.j` whose value is an `Edit` is a command:
22992586
23002587```haskell
2301−pr = \n -> describe n . squash
2588+pr = \n -> describe n . prev . squash
23022589```
23032590
23042591```
@@ -2311,9 +2598,9 @@ j 'pr "fix login"' && j 'push (label "fix-login" here)'
23112598
23122599Second parents of merge commits (merges are placed under their first parent
23132600and are read-only); hunk-level `split`; conflict resolution inside the
2314−language (blobs are opaque); workspaces other than the default; remotes other
2315−than `origin`; tags; submodules and LFS; ignore rules beyond `.gitignore`;
2316−changing file modes or symlinks from the language; repairing a repository in
2317−which one change id has two visible commits (refused; §7.2); general command
2318−flags beyond the first-word `--help` and `-h` aliases; static typing. None of
2601+language (blobs are opaque); workspaces other than the default; sparse working
2602+copies (refused; §7.1); remotes other than `origin`; tags; submodules and LFS;
2603+ignore rules beyond `.gitignore`; repairing a repository in which one change
2604+id has two visible commits (refused; §7.2); general command flags beyond the
2605+`--help` and `-h` discovery arguments; static typing. None of
23192606these are precluded by the design.