@@ -0,0 +1,1854 @@
3+`j` is a command-line interface to Jujutsu (jj) repositories. It is an
4+interpreter for a small, pure, dynamically typed functional language. A
5+repository is a value in that language; every command is an expression that
6+maps the current repository value to a new one, or to something to print.
8+Nothing is implicit beyond the language itself. Every shape, builtin, and
9+command is declared or defined in one user-owned file, `~/.config/j/config.j`,
10+written in the language. The binary provides the language (its syntax, its
11+operators and their fixities, its primitive kinds), the implementations of the
12+declared builtins, eight reserved words, and the bridge to jj repositories.
13+`j` does not require the `jj` binary; it uses jj's on-disk formats through
16+This document is the complete specification for implementing `j`. Section 9 is
17+the reference `config.j`; it is part of the specification and ships with the
29+`j` takes the expression from its arguments or from standard input.
31+1. If standard input is not a terminal and contains at least one byte, the
32+ expression is the entire content of standard input.
33+2. Otherwise the expression is the arguments joined with single spaces,
34+ verbatim. Nothing is quoted or wrapped on the user's behalf: a `Text` is
35+ a text only if it is quoted in the language, so a message is written
36+ `j 'describe "fix login"'` (or `j describe '"fix"'`), while `j new` is
37+ `new` and `j tree . squash` is `tree . squash`. Shell quoting is needed
38+ only for characters the shell owns: double quotes, parentheses, brackets,
39+ braces, a backslash, `<` `>` `|` `&` `;` `*` `$`. The operators `++`,
40+ `::`, `or`, `.`, and the literals `%name`, `@id`, `./a/b` are all safe
42+3. If arguments are present and rule 1 also applies, exit 2 with
43+ `j: expression given both as arguments and on stdin`.
44+4. If neither is present, print a one-line usage message to stderr and exit 2.
45+5. `j` accepts no flags. Any argument, including ones beginning with `-`, is
46+ part of the expression.
48+### 1.1 Reserved commands
50+Before the expression is parsed, its text is trimmed of leading and trailing
51+whitespace and split on whitespace into words. If the first word is one of the
52+eight reserved words below, the whole text is a reserved command.
54+| command | operation |
56+| `init` | create a repository in the current directory; §7.8 |
57+| `clone URL [DIR]` | clone `URL` into `DIR` (default: last path component of `URL`, minus `.git`); §7.8 |
58+| `remote URL` | set the URL of the remote `origin`, creating it if absent; §7.8 |
59+| `fetch` | fetch from `origin`; §7.6 |
60+| `push EXPR` | set or delete the remote bookmarks that `EXPR` selects; §7.6 |
61+| `undo` | restore the repository as it was before the newest operation; §7.7 |
62+| `redo` | reverse the newest undo; §7.7 |
63+| `ops` | print the operation log; §7.7 |
65+For every reserved command except `push`, the remaining words are literal
66+arguments (no quoting, no escaping); a wrong number is a usage error (exit 2)
67+and the language is not involved. For `push`, the remainder of the text is an
68+expression, evaluated as in §1.2 steps 2–7 but against the repository as
69+recorded, without snapshotting the working directory; its value must be a list
70+of push records (§7.6).
72+Matching is on the first word only, so an expression whose first token is one
73+of these words cannot be written on the command line. `(push)` or `x fetch`
74+are ordinary expressions. `config.j` may define functions with these names;
75+they are reachable only inside larger expressions.
77+### 1.2 Evaluating the expression
79+1. Locate the repository (§7.1). If none, exit 2.
80+2. Parse the expression (§3). On parse error, exit 3.
81+3. Load and validate `config.j` (§6), except for its `Id` literals. If it is
82+ missing or invalid, exit 3.
83+4. Build the current repository value `r` (§7.2). This includes snapshotting
84+ the working directory into the focused commit (§7.4).
85+5. Resolve every `Id` literal in `config.j` and in the expression (§4.10).
86+ A failure in `config.j` is a configuration error (exit 3); one in the
87+ expression is a crash (exit 1). Nothing has been evaluated.
88+6. Evaluate `config.j`'s definitions (§4.1), then the expression, to a value
90+7. If `v` is a function, apply it to `r`, once, and let `v` be the result.
91+8. If `v` is a `Repo` (§4.4): if it equals the repository value *as loaded,
92+ before the snapshot of §7.4*, do nothing at all; otherwise persist it
93+ (§7.5) and check out its focused commit's files (§7.4). Print nothing.
95+9. Otherwise display `v` (§5.1) on stdout. Persist nothing. Exit 0.
97+If evaluation crashes (§4.7) at any point, print the crash message to stderr,
98+persist nothing, leave the working directory untouched, and exit 1.
100+Step 7 applies exactly once. If the result of applying a function to `r` is
101+again a function, that function is displayed, not applied again.
103+Step 8's equality test means `j id` is a no-op when the working directory is
104+unchanged and records it when it is not: the snapshot makes `r` differ from
105+the loaded value, and that difference is what gets persisted. The test may be
106+implemented by comparing commit and tree hashes rather than values.
108+Two consequences worth knowing:
110+- **Dry run.** A `Text` result is displayed and persists nothing, and `.`
111+ composes a renderer after an edit, so `j tree . rebase trunk` shows
112+ what a rebase would produce without doing it. `validate` in the chain,
113+ `j tree . validate . rebase trunk`, additionally raises whatever
114+ persistence would refuse, so the preview is of something that could
115+ actually be persisted.
116+- **Do, then look.** `j squash` prints nothing. `j squash && j tree` is the
117+ idiom; there is no flag for it.
121+There is no timeout. A non-terminating expression runs until interrupted.
122+Interrupting `j` (SIGINT) kills it; because nothing is written until
123+evaluation completes, an interrupted run persists nothing and leaves the
124+working directory untouched.
131+| 1 | the expression crashed, or a reserved command was refused |
132+| 2 | usage error: bad arguments, no repository, unsupported repository (§7.2), another `j` running (§7.7) |
133+| 3 | configuration or parse error |
135+Errors are written to stderr as a single line beginning with `j: `. Crash
136+messages are the text given to `crash` (or generated by the interpreter),
137+prefixed with `j: crash: `, followed on a second line by the innermost base
138+definition that was executing and the top-level expression.
144+The language has these kinds of values. The primitive kinds are part of the
145+language and are not declared anywhere.
149+| `Int` | arbitrary-precision integer |
150+| `Text` | Unicode string |
151+| `Bool` | `true` or `false` |
152+| list | finite, ordered, homogeneous by convention, `[a b c]` |
153+| record | unordered set of named fields, `{ a = 1, b = "x" }` |
154+| function | closure, builtin, or partial application |
155+| `Id` | opaque: a jj change id. Literal `@kpqx` for an existing one (a unique prefix is enough); bare `@` for a new one (§4.10) |
156+| `Blob` | opaque: the content of one file, resolved or unresolved |
157+| `Shape` | a type name used as a value (§4.12) |
159+Records are structural. A record is not declared; it is whatever fields it
160+has. The shapes in `config.j` name field sets, and a record *is* a `Commit`,
161+say, exactly when its field set is `Commit`'s (§4.12). Two records are the
162+same value if they have the same field names with equal values.
168+### 3.1 Lexical structure
170+- **Comments.** `--` to end of line. `{-` … `-}`, nesting.
171+- **Identifiers.** `[a-z_][A-Za-z0-9_']*`. Keywords are excluded:
172+ `let in if then else or true false`.
173+- **Type names.** `[A-Z][A-Za-z0-9_]*`. Declared as shapes in `config.j`;
174+ usable in expressions as values (§4.12).
175+- **Integer literals.** `[0-9]+`. There is no unary minus; write `0 - 1`.
176+- **Text literals.** `"…"` with escapes `\"`, `\\`, `\n`, `\t`, `\r`.
177+- **Id literals.** `@` immediately followed by one or more of the letters
178+ `k`–`z` (jj's change-id alphabet), e.g. `@wqzt`: an existing id, given in
179+ full or by a unique prefix.
180+- **New id.** `@` followed by anything other than an identifier character
181+ is the token `NEWID`. `@` followed by an identifier character outside
182+ `k`–`z` (`@main`) is a lexical error.
183+- **Label literals.** `%` immediately followed by a git branch name
184+ (`[A-Za-z0-9._/-]+`, not starting with `.` or `-`, not containing `..`,
185+ not ending in `/` or `.lock`), e.g. `%main`.
186+- **Path literals.** `./` at the start of a token, extending to the next
187+ whitespace or one of `( ) [ ] { } , ;`: `./src/lexer.rs`. This rule runs
188+ before the selector and operator rules for `.`, so a `.` inside a path is
189+ part of the path. The literal denotes the list of components after the
190+ `./`; a trailing `/` is ignored and a bare `./` is `[]`, the root. Names
191+ the literal cannot spell are built with `splitOn "/"`.
192+- **Operators.** Exactly these tokens, and no others (§3.2):
193+ `. * + - ++ :: == /= < <= > >= && ||`, plus the keyword
194+ `or`, which is an operator syntactically.
195+- **Selector.** A `.` immediately followed (no whitespace) by an identifier is a
196+ single selector token `.name`. Any other `.` is the operator `.`.
197+- **Punctuation.** `( ) [ ] { } , ; : \ = -> _`. The colon `:` is punctuation:
198+ it separates a name from its type in declarations and record types, and
199+ appears nowhere else.
201+### 3.2 Operators and fixities
203+The operator set and its fixities are part of the language. No others exist
204+and none can be declared.
211+infix 4 == /= < <= > >=
217+Non-associative (`infix`) operators cannot be chained. Each symbolic operator
218+is still a builtin that must be declared in `config.j` to be usable (§6.2);
219+the table fixes how they parse, not whether they are in scope. `or` is a
220+keyword and is always available.
224+Two layout rules and nothing else.
226+1. **Top level.** A top-level item begins with a token in column 1. Every
227+ following line whose first token is in a column greater than 1 continues
228+ the item. Blank and comment-only lines are ignored.
229+2. **`let` blocks.** After `let`, the column of the first token opens a block.
230+ A subsequent line whose first token is in exactly that column begins a new
231+ binding; a line indented further continues the current binding; the block
232+ ends at `in` or at the first token in a smaller column. Bindings on a single
233+ line are separated by `;`. Blank and comment-only lines are ignored here
236+An expression given on the command line or on stdin is a single `expr`, not a
237+list of items, so rule 1 does not apply to it; rule 2 does.
243+item := typedecl | signature | definition
245+typedecl := TYPENAME '=' type
246+signature := (ident | '(' op ')') ':' type
247+definition := ident '=' expr
249+type := atype ('->' type)?
250+atype := TYPENAME | ident | '[' type ']' | '{' (ident ':' type (',' ident ':' type)*)? '}' | '(' type ')'
252+pattern := ident | '_'
254+expr := '\' pattern+ '->' expr
255+ | 'if' expr 'then' expr 'else' expr
256+ | 'let' bindings 'in' expr
258+bindings := binding ((NEWLINE|';') binding)*
259+binding := ident '=' expr
261+opexpr := app (op app)* -- resolved by the fixity table
262+app := postfix+ -- juxtaposition; left-associative
263+postfix := atom ( SELECTOR | '{' fields '}' )*
264+atom := ident | TYPENAME | INT | TEXT | IDLIT | NEWID | LABELLIT | PATHLIT | 'true' | 'false'
265+ | '(' expr ')' | '(' op ')' | '(' op expr ')' | '(' expr op ')'
269+fields := (ident '=' expr (',' ident '=' expr)*)?
274+- A definition binds a name to a value. Functions are values made with `\`;
275+ there is no separate function-definition syntax. `\x y -> e` takes two
277+- A signature immediately preceding a definition of the same name (blank
278+ lines and comments between are fine) documents and checks that definition
279+ (§4.13). A signature with no definition declares a builtin. At most one
281+- `(op e)` is a right section (`\x -> x op e`), `(e op)` a left section
282+ (`\x -> e op x`), `(op)` the operator as a function.
283+- A bare `SELECTOR` atom `.name` is the accessor function `\x -> x.name`.
284+- `postfix` binds tighter than application, as in Haskell: `f r { a = 1 }`
285+ is `f (r { a = 1 })`. Consequently a record *literal* passed as an argument
286+ after another atom must be parenthesised: `attach ({ root = c, children = [] }) r`.
287+- List elements are separated by whitespace, not commas, and each element is
288+ a `postfix` (an atom with selectors and updates): `[1 2 3]`,
289+ `["src" "lexer.rs"]`, `[{ root = c, children = [] }]`. An application or
290+ operator expression as an element is parenthesised: `[(up repo).root.id]`.
291+ Records keep commas.
292+- Lambda, `if`, `let`, and `or` extend as far right as possible.
293+- Patterns are variables and `_` only. No literal, list, or record patterns.
301+Strict, call by value, left to right. Function application is curried. All
302+top-level definitions in `config.j` and all bindings of one `let` block are
303+mutually recursive. A top-level definition is evaluated once, at load, in
304+dependency order: a definition is evaluated after every definition its body
305+refers to outside a lambda. A cycle among such references (two non-lambda
306+definitions each needing the other's value) is a configuration error;
307+references inside lambda bodies never form cycles, since a lambda is a value
308+before it is applied. The bindings of a `let` block are evaluated by the same
309+rule, with a cycle being a crash. A lambda body is evaluated on each
311+Recursion is unbounded; the interpreter must handle deep recursion without
312+stack overflow (or turn exhaustion into a crash).
316+Names resolve lexically: `let` bindings, then lambda parameters, then
317+top-level definitions and declared builtins. Field names are not in any
318+namespace; they occur only after `.`, in record literals, and in record
319+updates, so a field may be called `id` while `id` remains the identity
322+There is no shadowing. A `let` binding or lambda parameter may not use a name
323+already bound in an enclosing scope, including top-level definitions and
324+declared builtins; in `config.j` that is a configuration error (exit 3), on
325+the command line a parse error. Defining a top-level name twice, or defining a
326+name that is also a declared builtin, is a configuration error.
330+- `e.f` selects field `f`; crashes if absent.
331+- `e { f = v, … }` returns a copy with the listed fields replaced; crashes if
332+ any listed field is absent. Update never adds fields.
333+- Duplicate field names in a literal or update are a parse error.
335+### 4.4 Which values are a `Repo`
337+A value is treated as a `Repo` in §1.2 step 8 if and only if its shape is
338+`Repo` and its `root`'s shape is `Commit`, in the sense of §4.12 (exact field
339+sets). Deeper shape errors surface as crashes during persistence.
343+`==` is structural on ints, texts, bools, lists, and records; identity on
344+`Id`; content-and-mode on `Blob`; on shapes, equal field sets or the same
345+primitive kind. Comparing functions crashes. `/=` is its
346+negation. Ordering operators are defined on `Int` only and crash otherwise.
348+### 4.6 Special forms
350+`if`, `or`, `&&`, and `||` do not evaluate all operands. `&&` and `||`
351+short-circuit. The interpreter recognises `&&` and `||` by name; `or` is a
354+`a or b` evaluates `a`. If that crashes, the crash is discarded and `b` is
355+evaluated and returned. Otherwise, if the value of `a` is a function, `b` is
356+evaluated too, and if it is also a function the result is the function
357+`\x -> a x or b x`, with the same rule applying again to its result. In every
358+other case the result is the value of `a` and `b` is not evaluated. The
359+lifting is what makes `edit or id` mean "try the edit, keep the repository if
360+it crashes": a function value cannot crash until it is applied, so without
361+lifting the `or` would catch nothing.
365+`crash msg` aborts evaluation with `msg`. Every runtime error the interpreter
366+detects (unbound name, wrong kind of value, missing field, out-of-range index,
367+`head []`, `concat []`, comparing functions, non-`Bool` condition, arity or
368+kind errors in builtins) is a crash with an interpreter-generated message, and
369+is catchable by `or` exactly like a user crash. Nothing else is catchable: parse
370+and configuration errors, and `Id` literal resolution, happen before
375+Two builtins dispatch on the kind of an argument; functions compose with `.`:
377+| builtin | list | Text | function |
379+| `++` | concatenation | concatenation | crash |
380+| `concat` | flatten `[[a]] -> [a]` | join `[Text] -> Text` | crash |
382+`concat []` crashes (there is nothing to dispatch on). `map`, `filter`,
383+`length`, `null`, `head`, `tail`, `nth`, `take`, `drop`, `last`, `member`,
384+`foldl`, and `::` are defined on lists only.
388+The following table is normative for behaviour. Each builtin is available only
389+if `config.j` declares it (§6.2); the declared type is enforced at each call
394+| `.` | composition: `(f . g) x = f (g x)` |
396+| `const` | `const a b = a` |
398+| `==`, `/=` | §4.5 |
399+| `&&`, `||`, `not` | booleans; `&&`/`||` short-circuit |
400+| `+`, `-`, `*` | integer arithmetic |
401+| `<`, `<=`, `>`, `>=` | integer comparison |
402+| `show` | renders any value as text in literal syntax (§5.2) |
404+| `map`, `filter` | as usual |
405+| `length`, `null` | as usual |
406+| `head`, `tail`, `last` | crash on empty |
407+| `nth i xs` | 0-based; crash out of range |
408+| `take n`, `drop n` | clamp to list length |
409+| `member x xs` | membership by `==` |
410+| `range a b` | `[a (a+1) … (b-1)]`, empty if `b <= a` |
411+| `foldl f z xs` | left fold: `foldl f z [x y] = f (f z x) y`; `z` for `[]` |
412+| `concat`, `++` | §4.8 |
413+| `startsWith p t`, `endsWith s t` | text predicates |
414+| `splitOn sep t` | the pieces of `t` between occurrences of `sep`; `splitOn "/" "a/b"` is `["a" "b"]`, `splitOn "/" ""` is `[""]` |
415+| `replay onto ch` | replay the change `ch` (`{ from, to }`) onto the snapshot `onto`; §7.3 |
416+| `unresolved b` | `true` iff the blob is a conflict |
417+| `blob t` | a resolved regular-file blob with content `t` |
418+| `text b` | the content of a blob as text; for an unresolved blob, its conflict-marker rendering |
419+| `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 |
420+| `diff a b` | a unified diff from the text of `a` to the text of `b`, no header lines, three lines of context; `""` if equal; crashes if either is not UTF-8 |
421+| `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` |
422+| `treeWith o r` | the history rendered as a tree with options record `o`; §7.11 |
423+| `extract S v` | every subvalue of `v` that is an `S`; §4.12 |
424+| `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) |
425+| `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 |
427+### 4.10 Ids: existing and new
429+**Existing.** An `Id` literal `@kpqx` denotes the existing commit whose id
430+begins with `kpqx`. It is resolved before evaluation, not during it:
432+- In the command-line expression, every literal is resolved after parsing and
433+ before evaluation (§1.2 step 5), against the ids visible when the program
434+ started. A literal that matches no id or more than one is a crash that lists
435+ the candidates in commit-table form (§5.1); nothing is evaluated.
436+- In `config.j`, every literal is resolved at load, against the same ids. A
437+ literal that fails to resolve is a configuration error (exit 3) naming the
438+ definition it appears in.
440+After resolution a literal is an ordinary `Id` value holding the full id, so
441+`==` on ids is identity and never involves prefixes.
443+**New.** The bare token `@` is an expression that yields a distinct, freshly
444+minted `Id` each time it is evaluated. In a script that writes `@` once, this
445+is indistinguishable from replacing it with a new id before the run. In a
446+lambda body it is evaluated on each application, so `newCommit` (§9) mints a new
447+id per call. A top-level definition that is a bare record containing `@` is
448+evaluated once at load and holds one id for the whole program (§4.1). `show`
449+renders a minted id like any other.
451+### 4.11 Label literals
453+`%main` denotes `labelled "main"`, a `Revset`. It is a function, evaluated
454+when applied, so it never fails at parse time: a name the remote does not have
455+yields `[]`, and `goto %nope` crashes with "expected one revision, got 0" as
458+### 4.12 Shapes as values, and `extract`
460+A type name used in an expression evaluates to a *shape*: for a `typedecl`
461+whose type is a record, the set of its field names; for a primitive kind
462+(`Int`, `Text`, `Bool`, `Id`, `Blob`), that kind; for a `typedecl` that is an
463+alias of another type, that type's shape. A shape whose type is a function
464+(`Edit`, `Revset`), a list, or undeclared is a crash when used.
466+`extract S v` returns every subvalue of `v` that is an `S`, as a list. It
467+walks lists in order and records in ascending field-name order, descending
468+into every element and field; it does not enter functions or blobs. A record
469+is an `S` if its field set is exactly `S`'s (the same rule display uses to
470+recognise shapes); a primitive value is an `S` if it is of that kind. Matches
471+are collected in traversal order, and a matching record is also searched
472+inside for nested matches.
475+extract Commit repo -- every commit reachable from a Repo, Subtree, or Frame
476+extract Id repo -- every id in it
477+extract Push xs -- the Push records in a list, however nested
480+`extract` does not promise a meaningful order for values with several
481+positional fields; a `Repo` yields its `children` before its `context` before
482+its `root`. Where order matters, `commits (top repo)` is preorder.
486+Every application of a function that has a signature — a builtin or a
487+definition with a signature — is checked against it: each argument as it is
488+supplied, against the corresponding parameter type, and the result once the
489+application is complete (yields a non-function), against the result type.
490+Lambdas without a signature and the command-line expression are unchecked
491+except through the functions they call. A signature on a definition that is
492+not a function (`user`, `conflicts`) is checked once, against the value, when
493+the definition is evaluated at load.
495+The check is one level deep and never inspects inside a list or a record's
498+| declared type | check |
500+| `Int`, `Text`, `Bool`, `Id`, `Blob`, `Shape` | the value is of that kind |
501+| `[T]` | the value is a list; elements are not inspected |
502+| `{ a : T, … }`, or a shape name | the value is a record with exactly those field names |
503+| `A -> B` | the value is a function |
504+| a type variable | anything |
505+| an alias (`Edit`, `Revset`, `Snapshot`) | the type it stands for; an alias of a function type is unfolded so that further arguments and the final result are checked against it (`at : Revset -> Edit -> Edit` checks three arguments and a `Repo` result) |
507+Field values and list elements are not checked because that would make every
508+call cost the size of the value and would force lazily backed snapshots and
511+A violation is a crash, catchable by `or` like any other:
514+j: crash: contract: describe expected Text as argument 1, got Int
515+ in describe, from describe 3
518+For records the expected and actual field sets are shown:
519+`expected { files, labels, id, message }, got { children, context, root }`.
525+Two renderers. **Display** is what the CLI prints; it is designed to be read.
526+**`show`** is the builtin; it renders literal syntax designed to be pasted
527+back. They share glyphs and colours with `treeWith` (§7.11) and are
528+implemented beside it.
532+Every kind of value has a *line* form and a *block* form. The top-level value
533+prints in block form. Values nested inside another print in line form,
534+indented under their key or bullet. Colour follows §7.11's rules and is used
535+only when stdout is a terminal; nothing is conveyed by colour alone.
537+Shapes are recognised by their exact field set, as in §4.12: `Commit`, `Entry`,
538+`Subtree`, `Repo`, `Frame`, `Change`. Any other record is generic.
540+| kind | line form | block form |
542+| `Int` | decimal | same |
543+| `Bool` | `true` / `false` | same |
544+| `Text` | raw, first line, `…` if truncated | raw, all of it, trailing newline added if absent |
545+| `Id` | shortest unique prefix (bold), remainder (dim) | same |
546+| `Blob` | `‹214 B›`, `‹✖ 3.1 KB›` if unresolved | its content, raw (markers if unresolved) |
547+| function | name, lambda source, or `f arg …` | same |
548+| `Shape` | its type name | same |
549+| `Commit` | `glyph id message labels` — a `tree` line without rails | that line, then `author · age · n files`, then one changed path per line with `+ ~ − ✖` marks |
550+| `Entry` | `path size`, `✖` prefix if unresolved | same |
551+| `Subtree` | its top commit's line form | a tree rooted at it, no focus |
552+| `Repo` | its focus's line form | `treeWith` with the config's `tree` options |
553+| `Frame` | its parent's line form | parent's line, then its children's line forms in a row with `▢` at the hole |
554+| `Change` | `n paths` | one touched path per line with `+ ~ − ✖` marks, as in a `Commit` block |
555+| generic record | `{ a, b, … }` (field names only) | one field per line, names left-aligned and dim, values indented if multi-line |
556+| list of `Id` | `n commits` | a commit table: one commit line per id, in list order |
557+| list of generic records with identical field sets | `n rows` | a column table with a dim header; `true` shows as `✓`, `false` as blank; columns empty in every row are dropped |
558+| list of `[Text]` | `n paths` | one per line, joined with `/` |
559+| list of `Text` | `n items` | one per line |
560+| list of other scalars | `a, b, c` | same, wrapped at the terminal width |
561+| any other list | `n items` | one block per item, separated by a blank line |
563+An empty list of any kind prints `none` (dim) in block form and `0 …` in
564+line form. Author and age in a `Commit` block are read from jj's commit
566+they are not fields of the value. A list of `[Text]` prints as paths because
567+that is what such a list is in every base function; `show` is available
568+when the literal is wanted. Alignment in tables and columns uses display
569+width (East Asian wide characters count two cells, combining marks zero), not
578+conflicts src/lexer.rs
579+id wqzt·kpqxmnrvyxskptlmzzzzabcd
588+ id message labels focus
590+ kpqx add parser main
598+ Montelot · 2h ago · 2 files
605+✖ src/lexer.rs 3.1 KB
606+ src/parser.rs 8.7 KB
612+(`·` marks the bold/dim boundary in an id; there is no character there.)
614+Crashes print the message, then the innermost base definition that was
615+executing and the top-level expression:
618+j: crash: expected one revision, got 0
619+ in goto, from goto %nope
624+`show v` renders `v` as an expression in the language, such that for every
625+value except closures and unresolved blobs, `show v` parses and evaluates to a
626+value equal to `v` under the same `config.j`. It never uses colour and never
632+| `Text` | double-quoted, escaped as in §3.1 |
633+| `Bool` | `true` / `false` |
634+| list | `[v1 v2 …]` |
635+| record | `{ a = v, b = w }`, fields in ascending lexicographic order |
636+| `Id` | `@` followed by the full id, e.g. `@wqztkpqxmnrvyxskptlmzzzzabcd` |
637+| resolved `Blob` | `blob "…"` |
638+| unresolved `Blob` | `{- unresolved -} blob "…"` with the conflict-marker rendering |
639+| builtin | its declared name, operators parenthesised: `map`, `(++)` |
640+| top-level definition | its name |
641+| revset from a label literal | `%name` |
642+| shape | its type name: `Commit`, `Id` |
643+| lambda closure | its source text, exactly as written |
644+| partial application | the function's rendering followed by the arguments' renderings, each parenthesised if not atomic: `describe "wip"`, `at (parents) (describe "x")` |
646+Line breaking: one line if it fits in 80 columns; otherwise the outermost
647+list or record breaks one element per line, two-space indented, with leading
648+commas for records and nothing between list elements, recursively.
650+Since `show` returns a `Text` and a top-level `Text` displays raw,
651+`j show . focus` prints the literal of the focused commit, and
652+`show . f` does the same for any `f`.
658+### 6.1 Location and loading
660+`j` reads `$XDG_CONFIG_HOME/j/config.j`, defaulting to `~/.config/j/config.j`.
661+It is read by every invocation except `remote`, `fetch`, `undo`, `redo`, and
662+`ops`: expression evaluation and `push` need its definitions, `init` and
663+`clone` need `user`. When there is no repository yet (`init`, `clone`), only
664+`user` is evaluated; `Id` literals are not resolved and no other definition
665+is evaluated. If it does not exist, `j` exits 3 with
666+`j: no config at <path>; the reference config.j is installed at <share-path>`.
667+The binary is distributed with the reference `config.j` (§9) at
668+`<prefix>/share/j/config.j`; `j` never writes to the config path itself.
672+Loading fails (exit 3) if any of these hold:
674+1. The file does not parse (§3).
675+2. A signature without a definition names something the interpreter does not
676+ implement; or a name has two signatures; or a signature is not immediately
677+ followed by its definition when one exists.
678+3. A name is defined twice, or a definition uses the name of a builtin the
679+ interpreter implements (those names are reserved whether or not declared).
680+4. A `typedecl` name is declared twice.
681+5. An `Id` literal fails to resolve (§4.10).
682+6. `user`, `immutable`, `tree`, or `labelled` is absent; or `user` is not a
683+ record with non-empty `name` and `email` texts; or `immutable`, `tree`, or
684+ `labelled` is not a function.
686+Loading does **not** check types statically. Signatures are enforced at
687+each call, one level deep (§4.13). The *presence* of a signature with no
688+definition is what brings a builtin into scope; an implemented builtin that is
689+not declared is simply unbound, and using it is a runtime crash.
691+Definitions may reference each other in any order.
693+### 6.3 Definitions the interpreter reads
695+Four names in `config.j` are read by the interpreter itself, not only by
698+- `user`, a record `{ name, email }`: the identity that authors commits
700+- `immutable`, a `Revset` that persistence evaluates to decide what may not be
702+- `tree`, used by display to render a `Repo` (§5.1).
703+- `labelled`, which a label literal `%name` desugars to (§4.11).
705+All four are ordinary definitions; the reference `config.j` provides them and
706+a user edits or redefines them.
712+`j` operates on jj repositories through jj-lib. It creates jj commits,
713+operations, and working-copy state that `jj` itself can read, and vice versa,
714+but nothing here requires the `jj` binary to be installed.
716+### 7.1 Locating the repository
718+Walk up from the current directory to the first directory containing `.jj`.
719+Only the default workspace is supported; if the working directory belongs to
720+another workspace, exit 2.
722+### 7.2 Building the `Repo` value
724+The history of the repository is loaded at the current operation and mapped to
727+- **Commits.** Every visible commit (an ancestor of a visible head), including
728+ jj's root commit, becomes a `Commit` record: `files` from its tree (§7.3),
729+ `message` from its description, `labels` from the remote bookmarks of
730+ `origin` pointing at it (§7.6), `id` from its change id.
731+- **Tree.** The root commit is the top of the tree. Each commit's parent is
732+ its **first** jj parent. A commit with two or more parents (a merge) is
733+ therefore placed under its first parent and its other parents are not
734+ represented; such a commit is *read-only* (§7.5). If two visible commits
735+ share a change id, exit 2 with
736+ `j: change <id> has two visible commits; j cannot operate on this repository`.
737+- **Child order.** A commit's children are ordered by committer timestamp
738+ ascending, ties broken by change-id text ascending. Within a program,
739+ `attach` appends. The order is presentation only: no command and no law
740+ depends on which child is first, and the language has no way to select a
742+- **Focus.** The zipper is focused on the default workspace's working-copy
743+ commit, after snapshotting (§7.4). `context` is computed from the path to the
744+ root. If that commit is in the immutable set (§7.5) — typically because a
745+ `fetch` found the remote's `main` pointing at it — the snapshot cannot go
746+ into it; the focus is instead a new empty child of it holding the snapshot,
747+ as `abandon` would leave it, and persisting records that child. Nothing
748+ else is written by loading.
750+The record is the *semantics*. An implementation may back `files`, `children`,
751+and `context` with lazily materialised structures, provided every observation
752+an expression can make agrees with the eager value.
754+### 7.3 Files, blobs, and `replay`
756+- A commit's `files` has one `Entry` per tracked path in its tree. `path` is
757+ the list of path components; components must be valid UTF-8, and a
758+ repository with a path that is not exits 2. `content` is a `Blob` carrying the file's
759+ content and type (regular, executable, symlink) and, for conflicted paths,
760+ jj's conflict value for that path.
761+- A path that is deleted and resolved has no entry. A path that is in a
762+ deletion conflict has an entry whose blob is unresolved.
763+- `replay onto { from, to }` is jj's tree merge: for each path present in
764+ any of the three snapshots, the three-way merge of that path's values with
765+ `from` as the common ancestor, replaying the change from `from` to `to` on
766+ top of `onto`. Conflicts are kept as unresolved blobs, and resolved where
767+ jj's merge would resolve them (identical changes, changes from the current
768+ content, non-overlapping line-level hunks). It never crashes on content. It
769+ crashes if any of the three is not a well-formed snapshot (a list of `Entry`
770+ records with unique paths).
771+- `unresolved`, `blob`, and `text` are as in §4.9. `blob` produces a regular
772+ file. File type (executable, symlink) travels with a `Blob` but cannot be
773+ changed in the language. `text` of a symlink is its target; `text` of a
774+ non-UTF-8 blob crashes.
776+### 7.4 Working-directory invariant
778+At every program boundary the files of the focused commit equal the working
781+- **Before evaluation**, the working directory is snapshotted into the
782+ working-copy commit, and that is the `files` of the focus in the `Repo`
783+ value. The snapshot tracks every file not matched by `.gitignore` files,
784+ with no size limit, symlinks as symlinks; these rules are fixed, since `j`
785+ reads no jj configuration. If the snapshot changed the commit, that change
786+ is part of the operation persisted afterwards, or discarded with the rest if
787+ nothing is persisted. A crashed or printing program leaves the working copy
788+ and the repository exactly as before; `j id` is the way to record the
789+ working directory and nothing else (§1.2).
790+- **After persistence**, the focus of the result is checked out: its files are
791+ written to the working directory, unresolved blobs materialised with jj's
792+ conflict markers, and the workspace's working-copy commit set to the focus.
794+Reserved commands never snapshot.
798+Given the loaded value `old` and the result `new`, one jj operation is
799+recorded, described by the expression text (truncated to 200 characters).
802+1. **Validate shape.** `new` has the shape of a `Repo`; every `id` occurs
803+ exactly once; every snapshot has unique paths. Violations crash with a
804+ message naming the rule.
805+2. **Validate labels.** The set of `(id, label)` pairs in `new` equals the set
806+ in `old`. Labels are the remote's names (§7.6); a script cannot add, move,
807+ or remove one. In particular a commit carrying a label cannot be abandoned.
808+3. **Validate immutability.** The *immutable set* is the config's `immutable`
809+ revset evaluated against `old`, together with every merge commit (§7.2)
810+ and all of its ancestors. Every id in it must be present in `new` with the
811+ same parent id, files, and message. Violations crash naming the commit.
812+ (Ancestors of merges are included because rewriting an ancestor rewrites
813+ the merge, whose second parent the model cannot represent.)
814+4. **Walk `new` top-down.** For each commit `c` (root first):
815+ - If `c.id` is the root commit's change id, it must be the top of the tree
816+ and must be unchanged; otherwise crash.
817+ - If `c.id` is not in `old`: create a jj commit with parent = the jj commit
818+ written for `c`'s parent in this walk, tree from `c.files`, description
819+ `c.message`, author and committer from `user` (§7.9) and the current
820+ time, change id `c.id`.
821+ - Otherwise, if `c`'s parent's *written* commit hash, or `c.files`, or
822+ `c.message` differs from the stored commit: rewrite it (new commit hash,
823+ same change id, stored as a successor so jj records predecessors),
824+ keeping the author and author date, with committer `user` and the
826+ - Otherwise keep the stored commit.
827+5. **Abandon** every change id in `old` that is not in `new`.
828+6. **Focus.** The focus must be mutable: if `new.root.id` is the root commit
829+ or in the immutable set, crash with
830+ `the focus must be a mutable commit; compose with new`. Otherwise set the
831+ default workspace's working-copy commit to the commit carrying
832+ `new.root.id`. (`abandon` and `squash` end with `new` for this reason, so
833+ they are safe on a commit directly above `main`; navigation onto `main`
834+ is written `new . goto trunk`.)
835+7. Commit the operation, then perform the checkout of §7.4.
837+Labels are not written by persistence at all; they are derived from remote
838+refs, which only `fetch` and `push` change. The interpreter may run any part
839+of the walk through jj-lib's rebase machinery provided the stored result
840+equals what the steps above produce.
842+### 7.6 Labels and the remote
844+A label is the name of a bookmark on the remote `origin`, as of the last
845+`fetch` or `push`. There is one namespace. Labels are read-only in the
846+language: `fetch` and `push` are the only things that change them.
848+- **`fetch`** fetches from `origin` over git and imports refs. Afterwards
849+ each remote bookmark `name` is the label `name` on the commit it points at;
850+ labels for bookmarks that no longer exist on the remote are removed; commits
851+ reachable from remote bookmarks become visible. Fetching never moves the
852+ focus or touches the working directory. It records one operation.
853+- **`push EXPR`** evaluates `EXPR` (§1.1); a function result is applied to
854+ the recorded repository. The value must be a list whose elements are records
856+ - `{ id : Id, name : Text }` — set bookmark `name` to the commit `id`,
857+ creating it if absent;
858+ - `{ delete : Text }` — delete the bookmark of that name.
860+ Duplicates are merged; two records with the same `name` and different
861+ effects are a crash. It is refused (exit 1, nothing pushed) if:
862+ - a `name` is not a valid git branch name, an `id` is not a visible commit,
863+ or a delete names a bookmark the remote does not have;
864+ - any commit that would be sent has an unresolved file or an empty
866+ - the bookmark's current target is in the immutable set (§7.5) and the
867+ record would move it to a commit that does not descend from it, or delete
870+ Moving a bookmark whose current target is not immutable to an unrelated
871+ commit is allowed; that is how a rewritten stack is re-pushed. After a
872+ successful push the labels reflect the new positions. It records one
875+Only the remote named `origin` is supported in this version; `fetch` and
876+`push` exit 1 with `j: no remote origin` when it is not configured.
877+Authentication is git's: the ssh agent for ssh URLs, git credential helpers
878+for https, as jj-lib's git backend provides them; `j` adds no prompting.
879+Bringing work up to date is a separate expression, e.g. `rebase trunk`.
881+### 7.7 Operations, undo, redo, and the lock
883+Every persisting `j` program, and each of `fetch` and `push`, records exactly
884+one jj operation, described by the expression or command text. Crashes record
887+**Kinds.** `j` marks each operation it records, in the operation's metadata,
888+as *normal*, *undo* (naming the operation it undid), or *redo* (naming the
889+undo it reversed). Operations recorded by other tools are treated as normal,
890+except that an operation whose description carries jj's own undo or redo
891+marker is treated accordingly, so mixed use chains correctly where the marker
892+is present and degrades to "undo the newest operation" where it is not.
894+**`undo`** finds the operation to undo: starting at the head, while the
895+current operation is an undo, move to the operation it undid and then to that
896+operation's parent; while it is a redo, move past the operation it reapplied.
897+The first normal operation reached is undone by recording a new operation
898+that restores its parent's view, marked as an undo of it. If none is found,
899+exit 1 with `j: nothing to undo`.
901+**`redo`** requires the head to be an undo (possibly reached through redos);
902+it records an operation restoring the view of the operation that undo
903+discarded, marked as a redo. Otherwise exit 1 with `j: nothing to redo`.
905+Both refuse, before doing anything, if the working directory differs from the
906+focused commit's files: `j: working copy has changes not in @; run \`j id\`
907+to record them or discard them`. Both check out the restored focus afterwards.
908+Undoing a `push` restores the recorded labels and leaves the remote untouched.
910+**`ops`** prints the operation log, newest first, one line each: relative
911+time, the operation's description (a `j` expression, a command, or another
912+tool's description), and `undo`/`redo` where applicable; the current
913+operation is marked. Rendered in the binary like `tree`.
915+**Lock.** A run that may persist (any expression evaluation, `fetch`, `push`,
916+`undo`, `redo`) takes an exclusive lock, the file `.jj/j.lock`, for its
918+A second `j` exits 2 with `j: another j is running in this repository`.
919+Display-only runs are not distinguishable in advance, so they take the lock
920+too; only reserved commands that need no repository (`init`, `clone`) do not.
921+The lock prevents `j` from ever producing two commits with one change id.
922+Other tools operating concurrently are outside `j`'s control; if they produce
923+that state, §7.2 applies.
925+### 7.8 Creating and connecting repositories
927+- **`init`** creates a colocated repository (both `.jj` and `.git`) in the
928+ current directory. If `.git` already exists and `.jj` does not, jj is
929+ initialised on top of the existing git repository and its history is
930+ imported (subject to §7.2). Either way a working-copy commit is created as a
931+ child of the current head (or of the root commit for an empty repository)
932+ and checked out. It exits 2 if the directory is already inside a jj
933+ repository, or if `.git` exists but is not a directory (a git worktree or
934+ submodule). It requires `user` (§7.9).
935+- **`clone URL [DIR]`** creates `DIR` (exit 2 if it exists and is non-empty),
936+ clones `URL` into it as a colocated repository, sets `origin` to `URL`,
937+ fetches, and creates an empty working-copy commit as a child of the
938+ remote's default bookmark target (or of the root commit if the remote has
939+ none). It requires `user`.
940+- **`remote URL`** sets the URL of `origin`, creating the remote if it does not
941+ exist. It does not fetch.
945+Commits are authored and committed as `user.name <user.email>` from
946+`config.j` (§6.3). `j` reads no other configuration; jj-lib's settings object
947+is constructed by `j` from `user` and fixed defaults. `init`, `clone`, and
948+persistence exit 3 if `user` is missing or malformed, since that is a
951+### 7.10 External diff rendering
953+`difft p a b` runs difftastic. It is the only builtin that executes another
956+1. Write the content of `a` and `b` to two temporary files whose names end in
957+ the last component of `p` (so difftastic's language detection sees the
958+ real filename), inside a fresh temporary directory.
959+2. Run `difft OLD NEW` with the process's stdin closed and its environment
960+ inherited unchanged, except: if `DFT_COLOR` is unset and `j`'s stdout is a
961+ terminal, set `DFT_COLOR=always`; if `DFT_WIDTH` is unset and `j`'s stdout
962+ is a terminal, set it to the terminal width.
963+3. Capture stdout. Delete the temporary directory.
964+4. Return stdout as `Text`. A non-zero exit status is not an error (difftastic
965+ uses it to signal "differences found" under some options); a failure to
966+ execute the program is a crash naming it.
968+All rendering options are difftastic's own, set through its `DFT_*`
969+environment variables. `j` adds no options of its own. `diff` remains
970+available as a dependency-free unified diff; the reference `config.j` uses
971+`difft` for display, so a user without difftastic edits one line.
973+Packaging: difftastic is a runtime dependency; on Nix, wrap `j` with `difft`
976+### 7.11 Tree rendering
978+`treeWith options r` renders the history as text. It is implemented in the
979+binary because it is presentation, not semantics. It reads the `Repo` value,
980+the immutable set (§7.5), `diff` sizes, and (for the margin, ages, and the
981+sibling order) jj's commit metadata by id.
986+{ detail : Int -- 0, 1, or 2; see below
987+, margin : Bool -- age and author column
988+, elide : Bool -- fold uninteresting runs
989+, icons : Bool -- pictographic glyph set
990+, color : Text } -- "auto", "always", "never"
993+Commits with no stored counterpart — minted in this program, as in a dry
994+run — have no metadata: their margin cells are blank, their size bar is
995+computed from the value, and they sort last among their siblings. The same
996+holds for a `Commit` block in display (§5.1).
998+Missing fields crash. `color = "auto"` means colour iff stdout is a terminal.
999+If the environment variable `NO_COLOR` is set and non-empty, colour is never
1000+used regardless of `color`. `config.j` fixes the defaults by defining `tree`.
1004+Root at top, one line per commit, plus optional detail lines under the focus.
1009+ ◆ kpqx ▅ add parser main 3d mo
1010+ ├─ ○ mnrv ▂ fix lexer 1d mo
1011+ ▶ ├─ ⊗ wqzt ▃ wip feature 2h mo
1012+ │ ✖ src/lexer.rs ~ src/parser.rs + tests/lexer.rs
1013+ │ └─ ○ yxsk ▃ 1h mo
1014+ └─ ◌ ptlm docs ⋯ 3 5d ak
1017+(That is `detail = 2, margin = true`. With `detail = 1, margin = false`, the
1018+size bars, the margin, and the detail line are absent.)
1020+Columns, left to right:
1022+1. **Gutter**, two characters: `▶` on the focus line, blank otherwise.
1023+2. **Rails**: `│ ├─ └─`, two columns per depth; `╎ n` for an elided run
1025+3. **Node glyph**: what kind of commit.
1026+4. **Id**: shortest prefix unique among visible commits, minimum 4.
1027+5. **Size bar** (`detail ≥ 1`, focus and its parent and children only; all
1028+ commits at `detail = 2`): one of `▁ ▂ ▃ ▅ ▇`, lines added plus removed
1029+ against the parent, thresholds 1, 10, 50, 200, 1000. Absent for empty
1031+6. **Message**: first line; nothing if empty.
1032+7. **Labels**: right-aligned column, present only if any rendered commit has
1033+ a label; names separated by two spaces.
1034+8. **Margin** (`margin = true`): age and author initials, right-aligned.
1035+ Age is the committer time, rendered `Ns`, `Nm`, `Nh`, `Nd`, `Nw`, `Ny`.
1036+ Initials are the first letters of the author name's words, at most two.
1037+9. **Collapse marker** `⋯ n` after the message of a collapsed subtree's top.
1039+**Detail line** (`detail = 2`, focus only): indented under the focus, the
1040+changed paths with a mark each: `+` added, `~` modified, `−` deleted,
1041+`✖` unresolved. Renames are not detected; they appear as `−` and `+`.
1045+| glyph | meaning | `icons = true` |
1047+| `◉` | focus | `🌸` |
1048+| `●` | ancestor of the focus | `🌿` |
1049+| `○` | other commit | `🍃` |
1050+| `◆` | in the immutable set (§7.5) | `🪨` |
1051+| `◌` | empty: no change against its parent | `🫙` |
1052+| `⊗` | has unresolved files | `🔥` |
1053+| `⌂` | root | `🌱` |
1055+Precedence when several apply: `⌂`, then `⊗`, then `◌`, then `◆`, then the
1056+position glyphs; position is then carried by colour (and, for the focus, by
1059+#### Elision (`elide = true`)
1061+A commit is *interesting* if it is the focus, an ancestor of the focus, a
1062+child of an ancestor of the focus, labelled, conflicted, a leaf, or has more
1063+than one child. Runs of consecutive uninteresting commits on a single line of
1064+descent are folded into `╎ n`. Subtrees whose top commit is not the focus, an
1065+ancestor of it, or a child of an ancestor are rendered as their top commit
1066+followed by `⋯ n` (omitted when `n = 0`), where `n` counts hidden descendants.
1067+With `elide = false` everything is shown.
1071+Applied only when colour is on. Glyphs always carry the meaning; colour only
1072+adds emphasis, so a colourless rendering loses nothing.
1074+| element | treatment |
1076+| focus line | bold, faint background |
1077+| ancestors of the focus | normal |
1078+| all other lines | dim |
1079+| rails on the path from the top to the focus | cyan |
1080+| other rails, `╎ n`, `⋯ n` | dim |
1081+| id of a conflicted commit | red |
1082+| id of an immutable commit | blue |
1083+| message of an empty commit | dim italic |
1085+| size bar | one accent colour, brighter with size |
1086+| detail marks `+` `−` `~` `✖` | green, red, yellow, red |
1087+| age | grey scale: brighter when more recent |
1088+| author initials | a hue chosen per author, stable across runs (hash of the name) |
1090+Messages are never coloured except dim (cousins) and dim italic (empty).
1096+These hold for the reference `config.j` and should be property-tested against
1097+an in-memory implementation of the builtins (§10). `=` means the results are
1098+equal, or both crash.
1102+prev (next r) = r (when r has exactly one child)
1103+next (prev r) = r (when r is an only child)
1106+(abandon . new) r = r (up to the minted id of the new focus)
1107+(abandon . contract m . split m) r = r (up to minted ids)
1108+rebase parents r = r
1110+into i r = by i r (when i is a child of the focus)
1112+(e or id) r = r (when e r crashes)
1113+ancestors r = r.root.id :: ancestors (up r) (when up r exists)
1114+forEach (const []) e r = r
1116+replayOnto (up r).root r = r
1118+replay b { from = b, to = x } = x
1119+replay x { from = b, to = b } = x
1120+invert (invert ch) = ch
1121+touched (invert ch) = touched ch
1124+concat (xs ++ ys) = concat xs ++ concat ys (both non-empty)
1125+e or x = e (when e does not crash)
1131+## 9. Reference `config.j`
1133+This file is the complete definition of the language's vocabulary. It ships
1134+with the binary and is the version installed to `~/.config/j/config.j`.
1137+-- config.j — everything j knows.
1139+-- j reads this file on every run. An expression can use a name only if this
1140+-- file declares it (a builtin, implemented by j) or defines it (written here,
1141+-- in the language). Nothing else exists.
1143+-- Conventions in this file:
1144+-- Name = … a shape: a record layout, matched by its field names
1145+-- name : Type a signature. Alone, it declares a builtin implemented
1146+-- by j. Before a definition, it documents that definition.
1147+-- Either way j checks arguments and results against it at
1148+-- each call, one level deep: kind, list-ness, field names.
1149+-- name = expr a definition. Functions are lambdas: name = \x -> …
1151+-- Edits. Most definitions are Edits: functions from a Repo to a Repo. On the
1152+-- command line a function result is applied to the current repository; a Repo
1153+-- result is persisted. Anything else is printed. `e2 . e1` runs e1 then e2;
1154+-- `e or id` runs e and leaves things alone if it crashes.
1156+-- Four definitions here are read by j itself: `user` (who commits), `tree`
1157+-- (how a Repo is printed), `immutable` (which commits may never be
1158+-- rewritten), and `labelled` (what a %name literal means).
1161+------------------------------------------------------------------------------
1163+------------------------------------------------------------------------------
1165+user : { name : Text, email : Text }
1166+user = { name = "Your Name", email = "you@example.com" }
1169+------------------------------------------------------------------------------
1171+------------------------------------------------------------------------------
1173+Path = [Text] -- a file path as its components; written ./a/b
1174+Label = Text -- a bookmark name on the remote
1176+Entry = { path : Path, content : Blob }
1177+Snapshot = [Entry] -- the files of one commit; paths unique;
1178+ -- a deleted, resolved path has no entry
1179+Change = { from : Snapshot, to : Snapshot } -- the files before and after
1181+Commit = { files : Snapshot
1183+ , labels : [Label] -- the remote's names for this commit;
1184+ -- read-only: only fetch and push change them
1185+ , id : Id } -- each id is on exactly one commit
1187+Subtree = { root : Commit, children : [Subtree] }
1189+-- A Repo is the whole history, seen from one commit: the focused subtree plus
1190+-- the path back to the top. Each Frame is one ancestor with a hole where the
1191+-- focused subtree was; left and right are the siblings on either side. Sibling
1192+-- order is presentation only.
1193+Frame = { parent : Commit, left : [Subtree], right : [Subtree] }
1194+Repo = { root : Commit, children : [Subtree], context : [Frame] }
1195+ -- context = [] at the top of history
1197+Detached = { subtree : Subtree, rest : Repo }
1198+Rebase = { from : Snapshot, onto : Snapshot } -- the base a subtree was written on, and the new one
1199+Meta = { hash : Text, author : Text, email : Text, time : Int }
1200+ -- what jj records about a commit beyond the model
1202+Edit = Repo -> Repo -- a command; may crash
1203+Fileset = Path -> Bool -- which paths a command applies to
1204+Revset = Repo -> [Id] -- a set of commits, computed from a repo
1206+Push = { id : Id, name : Label } -- set the remote's bookmark `name` to `id`
1207+Drop = { delete : Label } -- delete the remote's bookmark
1210+------------------------------------------------------------------------------
1211+-- Builtins: functions and control
1212+------------------------------------------------------------------------------
1214+(.) : (b -> c) -> (a -> b) -> a -> c -- composition, right to left
1217+crash : Text -> a -- abort the program with a message
1219+-- `a or b` is a keyword, not a builtin: the value of a; or, if evaluating a
1220+-- crashed, the value of b. When a is a function and b is one too, the result
1221+-- is pointwise, (f or g) x = f x or g x, so `edit or id` tries an edit and
1222+-- keeps the repository if it fails. Edits compose with `.`: `tree . squash`
1223+-- squashes, then renders.
1225+------------------------------------------------------------------------------
1226+-- Builtins: comparison, booleans, integers
1227+------------------------------------------------------------------------------
1229+(==) : a -> a -> Bool -- structural; functions crash
1230+(/=) : a -> a -> Bool
1231+(&&) : Bool -> Bool -> Bool -- short-circuit
1232+(||) : Bool -> Bool -> Bool -- short-circuit
1234+(+) : Int -> Int -> Int
1235+(-) : Int -> Int -> Int
1236+(*) : Int -> Int -> Int
1237+(<) : Int -> Int -> Bool
1238+(<=) : Int -> Int -> Bool
1239+(>) : Int -> Int -> Bool
1240+(>=) : Int -> Int -> Bool
1241+show : a -> Text -- any value, in this language's syntax
1243+------------------------------------------------------------------------------
1244+-- Builtins: lists and text
1245+------------------------------------------------------------------------------
1247+(::) : a -> [a] -> [a] -- cons
1248+map : (a -> b) -> [a] -> [b]
1249+filter : (a -> Bool) -> [a] -> [a]
1252+head : [a] -> a -- crash on []
1253+tail : [a] -> [a] -- crash on []
1254+last : [a] -> a -- crash on []
1255+nth : Int -> [a] -> a -- 0-based; crash out of range
1256+take : Int -> [a] -> [a]
1257+drop : Int -> [a] -> [a]
1258+member : a -> [a] -> Bool
1259+range : Int -> Int -> [Int] -- range a b = [a (a+1) … (b-1)]
1260+foldl : (b -> a -> b) -> b -> [a] -> b -- foldl f z [x y] = f (f z x) y
1262+-- ++ and concat work on lists and on text. concat [] crashes: supply a
1263+-- default with or, e.g. concat xs or []
1267+startsWith : Text -> Text -> Bool
1268+endsWith : Text -> Text -> Bool
1269+splitOn : Text -> Text -> [Text] -- splitOn "/" "a/b" = ["a" "b"]
1271+------------------------------------------------------------------------------
1272+-- Builtins: the repository
1273+------------------------------------------------------------------------------
1275+-- Replay a change onto a snapshot: "onto, with the change from `from` to `to`
1276+-- applied." Total: where the change collides with what is already there, the
1277+-- path's blob is unresolved rather than an error.
1278+replay : Snapshot -> Change -> Snapshot
1279+unresolved : Blob -> Bool
1280+blob : Text -> Blob -- a resolved regular file
1281+text : Blob -> Text -- its content (markers if unresolved)
1282+by : Id -> Repo -> Repo -- focus the commit with this id; crash if absent
1283+meta : Id -> Meta -- git hash, author, email, committer time;
1284+ -- crash for a commit not yet persisted
1285+validate : Repo -> Repo -- the repo unchanged, or the crash persisting
1286+ -- it would produce (shape, labels, immutability,
1287+ -- focus). `tree . validate . rebase trunk` is an
1289+diff : Blob -> Blob -> Text -- unified diff from the first to the second
1290+difft : Path -> Blob -> Blob -> Text -- difftastic's rendering; needs difft on PATH.
1291+ -- Options via DFT_* environment variables.
1292+treeWith : { detail : Int, margin : Bool, elide : Bool, icons : Bool, color : Text }
1293+ -> Repo -> Text -- the history as a tree; see the spec
1295+-- Every subvalue of a given shape, however deeply nested. A shape is a type
1296+-- name used as a value: extract Commit x, extract Id x, extract Push ys.
1297+extract : Shape -> a -> [b]
1299+-- Ids: an existing commit is written @kpqx (any unique prefix; checked before
1300+-- the program runs). A bare @ is a new id, distinct each time it is evaluated.
1301+-- A label is written %main and is a Revset: the commit the remote calls "main".
1304+------------------------------------------------------------------------------
1305+-- Zipper. A Repo is focused on one commit; these move or detach the focus.
1306+-- Commands never select a commit by position: use ids, labels, or predicates.
1307+------------------------------------------------------------------------------
1309+-- To the parent. Crashes at the top.
1312+ let f = head repo.context
1313+ in { root = f.parent
1314+ , children = f.left ++ [{ root = repo.root, children = repo.children }] ++ f.right
1315+ , context = tail repo.context }
1317+-- Detach the focused subtree; the focus moves to the parent. Crashes at the top.
1318+detach : Repo -> Detached
1320+ let f = head repo.context
1321+ in { subtree = { root = repo.root, children = repo.children }
1322+ , rest = { root = f.parent, children = f.left ++ f.right, context = tail repo.context } }
1324+-- To the top of history.
1326+top = \repo -> top (up repo) or repo
1328+-- Every commit of a subtree (or of the focused subtree of a Repo), in preorder.
1329+-- Takes anything with `root` and `children`, so the signature is left open.
1330+commits : a -> [Commit]
1331+commits = \t -> t.root :: (concat (map commits t.children) or [])
1334+------------------------------------------------------------------------------
1335+-- Structure. Editing at the focus.
1336+------------------------------------------------------------------------------
1338+mapRoot : (Commit -> Commit) -> Edit
1339+mapRoot = \f repo -> repo { root = f repo.root }
1341+mapChildren : ([Subtree] -> [Subtree]) -> Edit
1342+mapChildren = \f repo -> repo { children = f repo.children }
1344+-- Attach a subtree as a new child of the focus; the focus does not move.
1345+attach : Subtree -> Edit
1346+attach = \t -> mapChildren (++ [t])
1348+-- Remove the focused commit; its children join the parent. Focus to the parent.
1350+remove = \repo -> let x = detach repo
1351+ in x.rest { children = x.rest.children ++ x.subtree.children }
1353+-- Attach a commit as a new child of the focus, and move to it.
1354+addChild : Commit -> Edit
1355+addChild = \c repo -> by c.id (attach ({ root = c, children = [] }) repo)
1357+-- A new commit with these files, no message, no labels, and a new id.
1358+newCommit : Snapshot -> Commit
1359+newCommit = \files -> { files = files, message = "", labels = [], id = @ }
1362+------------------------------------------------------------------------------
1363+-- Snapshots, changes, and filesets
1364+------------------------------------------------------------------------------
1366+-- A path is written ./src/lexer.rs, which is ["src" "lexer.rs"]; ./ alone is
1367+-- the root, []. For names the literal cannot spell, splitOn "/" "…" builds one.
1369+restrict : Fileset -> Snapshot -> Snapshot -- entries matching m
1370+restrict = \m -> filter (\e -> m e.path)
1372+select : Fileset -> Snapshot -> Snapshot -> Snapshot -- m from a, the rest from b
1373+select = \m a b -> restrict m a ++ restrict (neg m) b
1375+conflicted : Snapshot -> [Path]
1376+conflicted = \s -> map (.path) (filter (\e -> unresolved e.content) s)
1378+-- What the focus changes: its parent's files, and its own.
1379+changeOf : Repo -> Change
1380+changeOf = \repo -> { from = (up repo).root.files or [], to = repo.root.files }
1382+-- The same change, backwards.
1383+invert : Change -> Change
1384+invert = \ch -> { from = ch.to, to = ch.from }
1386+everything : Fileset
1387+everything = const true
1389+neg : Fileset -> Fileset
1392+both : Fileset -> Fileset -> Fileset
1393+both = \m n p -> m p && n p
1395+either : Fileset -> Fileset -> Fileset
1396+either = \m n p -> m p || n p
1398+under : Path -> Fileset -- inside directory d
1399+under = \d p -> take (length d) p == d
1401+ext : Text -> Fileset -- file extension e
1402+ext = \e p -> endsWith ("." ++ e) (last p)
1405+------------------------------------------------------------------------------
1406+-- Revsets. A revset is a function from a Repo to a list of ids, so it can be
1407+-- relative to the focus (parents, kids) or absolute (all, %main).
1408+------------------------------------------------------------------------------
1411+here = \repo -> [repo.root.id]
1414+parents = \repo -> [(up repo).root.id] or []
1417+kids = \repo -> map (\t -> t.root.id) repo.children
1419+descendants : Revset -- includes the focus
1420+descendants = \repo -> map (.id) (commits repo)
1422+ancestors : Revset -- includes the focus
1423+ancestors = \repo -> repo.root.id :: ((let p = up repo in ancestors p) or [])
1426+siblings = \repo -> filter (\i -> i /= repo.root.id) (kids (up repo)) or []
1429+all = \repo -> descendants (top repo)
1431+stack : Revset -- the line through the focus
1432+stack = union ancestors descendants
1434+labelled : Label -> Revset -- %main is labelled "main"
1435+labelled = \n repo -> map (.id) (filter (\c -> member n c.labels) (commits (top repo)))
1437+-- Evaluate f starting from each commit of rs, and collect.
1438+from : Revset -> Revset -> Revset
1439+from = \rs f repo -> concat (map (\i -> f (by i repo)) (rs repo)) or []
1441+descendantsOf : Revset -> Revset
1442+descendantsOf = \rs -> from rs descendants
1444+ancestorsOf : Revset -> Revset
1445+ancestorsOf = \rs -> from rs ancestors
1447+union : Revset -> Revset -> Revset
1448+union = \a b repo -> let xs = a repo in xs ++ filter (\i -> not (member i xs)) (b repo)
1450+intersect : Revset -> Revset -> Revset
1451+intersect = \a b repo -> let ys = b repo in filter (\i -> member i ys) (a repo)
1453+minus : Revset -> Revset -> Revset
1454+minus = \a b repo -> let ys = b repo in filter (\i -> not (member i ys)) (a repo)
1456+matching : (Commit -> Bool) -> Revset -> Revset
1457+matching = \p rs repo -> filter (\i -> p (commitAt i repo)) (rs repo)
1460+conflicts = matching (\c -> not (null (conflicted c.files))) all
1462+-- The first of several revsets that is non-empty.
1463+firstOf : [Revset] -> Revset
1464+firstOf = \rss repo -> foldl (\acc rs -> if null acc then rs repo else acc) [] rss
1466+-- The main line: whichever of these names the remote has, first one wins.
1467+-- Empty in a repository with no remote, in which case nothing is immutable.
1469+trunk = firstOf [%main %master %trunk]
1471+-- Commits that may never be rewritten, abandoned, or focused. j evaluates this
1472+-- before persisting anything and refuses to change what it names. Merge
1473+-- commits and their ancestors are immutable regardless. The focus must always
1474+-- be mutable, so it is `new . goto trunk`, never `goto trunk` alone.
1476+immutable = ancestorsOf trunk
1479+------------------------------------------------------------------------------
1480+-- Focusing. `goto` is the only place a revset must have exactly one member.
1481+------------------------------------------------------------------------------
1483+goto : Revset -> Edit
1486+ in if length is == 1
1487+ then by (head is) repo
1488+ else crash ("expected one revision, got " ++ show (length is))
1490+commitAt : Id -> Repo -> Commit
1491+commitAt = \i repo -> (by i repo).root
1493+prev : Edit -- the parent
1496+next : Edit -- the only child
1499+tip : Edit -- end of the line of only-children
1500+tip = \repo -> tip (next repo) or repo
1502+into : Id -> Edit -- a child, by id
1503+into = \i repo -> if member i (kids repo) then by i repo
1504+ else crash "into: not a child of the focus"
1506+child : (Commit -> Bool) -> Edit -- the one child satisfying p
1507+child = \p -> goto (matching p kids)
1509+-- Run an edit somewhere else, then come back (by id, so the edit may move things).
1510+at : Revset -> Edit -> Edit
1511+at = \rs e repo -> let me = repo.root.id in by me (e (goto rs repo))
1513+-- Run an edit at every commit of a revset, returning to the focus.
1514+forEach : Revset -> Edit -> Edit
1515+forEach = \rs e repo -> foldl (\acc i -> at (const [i]) e acc) repo (rs repo)
1517+eachChild : Edit -> Edit
1518+eachChild = forEach kids
1520+guard : (Repo -> Bool) -> Edit -- crash unless p holds
1521+guard = \p repo -> if p repo then repo else crash "guard"
1523+clean : Repo -> Bool
1524+clean = \repo -> null (conflicted repo.root.files)
1527+------------------------------------------------------------------------------
1529+------------------------------------------------------------------------------
1531+-- Rewrite a subtree as if it had been written on `onto` instead of `from`.
1532+-- Each commit's change is replayed onto the new base; its children are then
1533+-- replayed from its old files onto its new ones.
1534+replayTree : Rebase -> Subtree -> Subtree
1535+replayTree = \rb t ->
1537+ c' = c { files = replay rb.onto ({ from = rb.from, to = c.files }) }
1538+ in { root = c', children = map (replayTree ({ from = c.files, onto = c'.files })) t.children }
1540+-- Replay the focused subtree onto the commit newP, in place. The subtree stays
1541+-- attached where it is; only its files change. Move it afterwards with detach
1542+-- and attach. Crashes at the top.
1543+replayOnto : Commit -> Edit
1544+replayOnto = \newP repo ->
1545+ let t = replayTree ({ from = (up repo).root.files, onto = newP.files })
1546+ ({ root = repo.root, children = repo.children })
1547+ in repo { root = t.root, children = t.children }
1549+-- Replace the focused commit with f applied to it, replaying its children.
1550+rewrite : (Commit -> Commit) -> Edit
1551+rewrite = \f repo ->
1554+ in (eachChild (replayOnto c') repo) { root = c' }
1557+------------------------------------------------------------------------------
1559+------------------------------------------------------------------------------
1561+-- Set the message of the focused commit.
1562+describe : Text -> Edit
1563+describe = \m -> mapRoot (\c -> c { message = m })
1565+-- Start an empty child of the focus and move to it.
1567+new = \repo -> addChild (newCommit repo.root.files) repo
1569+-- Remove the focused commit. Its children are rebased onto its parent, and a
1570+-- new empty commit on that parent becomes the focus (the parent may be
1571+-- immutable, and the focus never is). Refused for a commit the remote has a
1572+-- name for: push something else under that name first, or forget the name.
1577+ in if not (null c.labels)
1578+ then crash ("abandon: the remote names this commit " ++ show c.labels)
1579+ else new (remove (eachChild (replayOnto p) repo))
1581+-- Push the files matching m from the focus down into its parent.
1582+contract : Fileset -> Edit
1583+contract = \m repo -> let a = repo.root
1584+ in at parents (rewrite (\q -> q { files = select m a.files q.files })) repo
1586+-- Fold the focus entirely into its parent; a new empty commit becomes the focus.
1588+squash = abandon . contract everything
1590+-- Push the files matching m from the focus up into a new child, which becomes
1591+-- the focus and inherits the old focus's children. Labels stay with the
1592+-- original commit, since they are the remote's.
1593+split : Fileset -> Edit
1597+ a' = a { files = select m p.files a.files }
1598+ x = newCommit a.files
1599+ in by x.id (repo { root = a', children = [{ root = x, children = repo.children }] })
1601+-- Move the focused subtree onto another commit. Returns to the moved commit.
1602+rebase : Revset -> Edit
1603+rebase = \rs repo ->
1604+ let d = (goto rs repo).root
1605+ in if member d.id (descendants repo)
1606+ then crash "rebase: the destination is inside the subtree being moved"
1607+ else let me = repo.root.id
1608+ x = detach (replayOnto d repo)
1609+ in by me (attach x.subtree (by d.id x.rest))
1611+-- Apply the focused commit's change onto another commit, as a new child there.
1612+pick : Revset -> Edit
1614+ let ch = changeOf repo
1615+ t = (goto rs repo).root
1616+ in at rs (addChild (newCommit (replay t.files ch))) repo
1618+-- A new child of the focus that undoes the change of the given commit.
1619+backout : Revset -> Edit
1620+backout = \rs repo ->
1621+ let ch = changeOf (goto rs repo)
1622+ in addChild (newCommit (replay repo.root.files (invert ch))) repo
1625+------------------------------------------------------------------------------
1626+-- Pushing. `j push EXPR` evaluates EXPR to a list of Push and Drop records.
1627+------------------------------------------------------------------------------
1629+-- Label every commit of a revset. `j 'push (label "feature" here)'` labels @.
1630+label : Label -> Revset -> Repo -> [Push]
1631+label = \n rs repo -> map (\i -> { id = i, name = n }) (rs repo)
1633+-- Remove a label on the remote. `j 'push (unlabel "feature")'` after the merge.
1634+unlabel : Label -> [Drop]
1635+unlabel = \n -> [{ delete = n }]
1637+-- Move a label: `j 'push (rename "old" "new")'`.
1638+rename : Label -> Label -> Repo -> [b]
1639+rename = \old new repo -> label new (labelled old) repo ++ unlabel old
1641+-- Every label the remote already has, at its current local commit.
1642+-- `j push relabel` after rewriting a stack moves the remote's bookmarks along.
1643+relabel : Repo -> [Push]
1644+relabel = \repo -> concat (map (\c -> map (\n -> { id = c.id, name = n }) c.labels)
1645+ (extract Commit repo)) or []
1648+------------------------------------------------------------------------------
1649+-- Inspection. These return printable values rather than a Repo.
1650+------------------------------------------------------------------------------
1652+focus : Repo -> Commit -- the focused commit
1653+focus = \repo -> repo.root
1655+files : Repo -> Snapshot -- its files
1656+files = \repo -> repo.root.files
1658+-- The entry at path p in snapshot s, as a list: [] if absent.
1659+entryAt : Path -> Snapshot -> [Entry]
1660+entryAt = \p s -> filter (\e -> e.path == p) s
1662+-- The content at path p, or an empty file if absent.
1663+contentAt : Path -> Snapshot -> Blob
1664+contentAt = \p s -> (head (entryAt p s)).content or blob ""
1666+-- The paths a change touches.
1667+touched : Change -> [Path]
1669+ let paths = map (.path) ch.to ++ filter (\p -> not (member p (map (.path) ch.to))) (map (.path) ch.from)
1670+ in filter (\p -> entryAt p ch.to /= entryAt p ch.from) paths
1672+-- Paths whose content differs between the focus and its parent.
1673+changed : Repo -> [Path]
1674+changed = \repo -> touched (changeOf repo)
1676+-- What the focus changes, rendered by difftastic, one record per changed path.
1677+-- Replace difft with (\p -> diff) to use the built-in unified diff instead.
1678+diffs : Repo -> [{ path : Path, diff : Text }]
1680+ let ch = changeOf repo
1681+ in map (\p -> { path = p, diff = difft p (contentAt p ch.from) (contentAt p ch.to) }) (touched ch)
1683+-- The same, as one page of text. A Text result prints raw, so `j review` is
1684+-- readable in the terminal.
1685+review : Repo -> Text
1686+review = \repo -> concat (map (\d -> d.diff) (diffs repo)) or "no changes\n"
1688+-- A summary of the focus.
1689+status : Repo -> { id : Id, message : Text, labels : [Label], changed : [Path], conflicts : [Path] }
1691+ { id = repo.root.id
1692+ , message = repo.root.message
1693+ , labels = repo.root.labels
1694+ , changed = changed repo
1695+ , conflicts = conflicted repo.root.files }
1697+-- A summary of every commit, top first, in preorder.
1698+log : Repo -> [{ id : Id, message : Text, labels : [Label], focus : Bool }]
1700+ map (\c -> { id = c.id
1701+ , message = c.message
1702+ , labels = c.labels
1703+ , focus = c.id == repo.root.id })
1704+ (commits (top repo))
1706+-- The history as a tree. Edit these records to change what `j tree` shows.
1707+-- (The parentheses are required: `f { … }` would be a record update of f.)
1709+tree = treeWith ({ detail = 1, margin = false, elide = true, icons = false, color = "auto" })
1711+treeFull : Repo -> Text
1712+treeFull = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto" })
1719+The interpreter must be testable without a jj repository. Provide an in-memory
1720+implementation of the domain builtins (`replay`, `unresolved`, `blob`,
1721+`text`, `by`, `extract`, `meta`, `difft` as a stub, and the minting
1722+of `@`) in which a blob is text, a
1723+conflict is a record of the three sides, and `replay` resolves a path exactly
1724+when at most one side changed it or both made the same change. Property-test
1725+the laws of §8 over randomly generated `Repo` values (bounded depth and width,
1726+random labels, random file edits) using the reference `config.j` unmodified.
1728+The builtin `by` must agree, on every generated repo, with this in-language
1729+reference definition, which uses positional moves the public base does not
1734+ let k = nth i repo.children
1735+ in { root = k.root, children = k.children
1736+ , context = { parent = repo.root, left = take i repo.children, right = drop (i + 1) repo.children }
1738+locs = \l -> l :: (concat (map (\i -> locs (down i l)) (range 0 (length l.children))) or [])
1739+by = \i repo -> head (filter (\l -> l.root.id == i) (locs (top repo))) or crash "by: no such commit"
1742+Golden tests: parse the reference `config.j`; `show` round-trips through the
1743+parser for generated values of every kind except closures; every example in
1744+§11 produces the stated result.
1748+## 11. The development flow, end to end
1750+Each step of ordinary development, and what covers it. Anything not listed
1751+here is out of scope (§12). Nothing here uses the `jj` binary.
1753+**Getting a repository**
1756+$EDITOR ~/.config/j/config.j -- set `user` once
1757+j init -- new repository here, or adopt an existing .git
1758+j clone git@host:org/repo.git -- or an existing one
1759+j remote git@host:org/repo.git -- attach a remote to an init'd repo
1765+j tree -- the history as a tree, focus marked
1766+j treeFull -- the same with sizes, ages, authors, changed paths
1767+j status -- id, message, labels, changed paths, conflicts of @
1768+j log -- every commit as a record
1769+j review -- difftastic rendering of @ against its parent
1770+j review . by @kpq -- the same for another commit, without moving
1771+j diffs -- the same, one record per path
1773+j kids -- @'s children, as a commit table
1774+j conflicts -- every conflicted commit
1775+j 'map (.path) . files' -- paths tracked in @
1776+j 'text . contentAt ./README.md . files'
1777+j focus -- the focused commit
1778+j 'meta . (.id) . focus' -- its git hash, author, time
1784+j 'describe "fix login"'
1785+j new -- start the next commit
1786+j prev -- to the parent
1787+j next -- to the only child
1788+j new . goto trunk -- start on the main line; the focus must be a mutable commit
1789+j by @kpq -- any unique id prefix
1790+j into @kpq -- a child, by id
1791+j 'child (\c -> startsWith "fix" c.message)'
1792+j 'split (ext "rs")' -- *.rs changes into a new child
1793+j 'contract (under ./docs)' -- docs/ changes down into the parent
1797+j guard clean . rebase trunk or id -- only if it comes out clean
1799+j 'forEach (descendantsOf here) (describe "wip")'
1800+j id -- record the working directory, change nothing else
1801+j tree . squash -- dry run: show the result, persist nothing
1802+j tree . validate . squash -- the same, refusing what persistence would refuse
1803+j squash && j tree -- do, then look
1809+j 'push (label "feature" here)' -- name @ on the remote
1810+j push relabel -- move the remote's existing names to the rewritten stack
1811+j 'push (unlabel "feature")' -- delete a name on the remote
1812+j 'push (rename "old" "new")' -- move a name
1813+j fetch -- refresh the remote's names and commits
1814+j rebase trunk -- bring the stack up to date
1819+A rebase or fetch that conflicts leaves unresolved files in the affected
1820+commits. `j conflicts` lists them; `j by @…` moves there, which checks the
1821+files out with conflict markers; editing the files and running any persisting
1822+expression (`j id` at minimum) records the resolution.
1827+j ops -- what happened, newest first
1828+j undo -- reverts one j expression
1834+Any definition added to `config.j` whose value is an `Edit` is a command:
1837+pr = \n -> describe n . squash
1841+j 'pr "fix login"' && j 'push (label "fix-login" here)'
1846+## 12. Out of scope for this version
1848+Second parents of merge commits (merges are placed under their first parent
1849+and are read-only); hunk-level `split`; conflict resolution inside the
1850+language (blobs are opaque); workspaces other than the default; remotes other
1851+than `origin`; tags; submodules and LFS; ignore rules beyond `.gitignore`;
1852+changing file modes or symlinks from the language; repairing a repository in
1853+which one change id has two visible commits (refused; §7.2); a `--help` flag
1854+or any flag at all; static typing. None of these are precluded by the design.