on master help sign in
j/docs/tutorial.md
538 lines 17 kB history raw
1# A tutorial on `j`
3This is a guided first hour with `j`. It builds the mental model piece by
4piece — not just *what* to type, but *why it works* — so that by the end you
5can compose your own commands instead of memorizing them.
7You'll want `j` installed (`nix build` or `cargo build --release`). Set up
8your configuration with your own name and email:
10```
11j setup 'Ada Lovelace' ada@example.org
12```
14This creates the full reference configuration without overwriting an existing
15file. If you already have a config, `j help config` shows its location so you
16can edit the `user` definition. The remaining commands run in a real
17repository, so you can follow along.
19---
21## Part 1 — Ten minutes to a working repository
23Make an empty directory and create a repository:
25```
26mkdir play && cd play
27j init
28```
30`j init` makes a colocated `.jj`/`.git` repository and checks out a fresh
31working-copy commit: the commit you are currently editing. It already exists,
32so write a file and describe that commit:
34```
35echo 'fn main() {}' > main.rs
36j 'describe "start the project"'
37```
39Look at what you have:
41```
42j 'tree'
43```
45```
46 ⌂ zzzz
47▶ └─ ◉ pnyz ▂ start the project
48```
50Add a second change on top:
52```
53j 'new'
54echo 'lib' > lib.rs
55j 'describe "add a library"'
56j 'tree'
57```
59```
60 ⌂ zzzz
61 └─ ● pnyz ▂ start the project
62▶ └─ ◉ psnq ▂ add a library
63```
65That's the basic loop: **edit files, describe the current change, then `new`
66before editing the next change.** `new` opens an empty change with the same
67files as its parent; it does not move existing edits into that new change.
68There is no `git add` step. `describe` saves both the current files and their
69description; `j save` records file edits without changing the description.
71**What just happened, in the model:** each expression snapshots your working
72directory for evaluation, builds a `Repo` value focused on that commit,
73evaluates your expression, and — because `new` and `describe` are *edits*
74(functions `Repo -> Repo`) — persists the resulting repository.
76`j status` has two useful lists: `in commit` compares the current commit with
77its parent, while `unrecorded` lists file edits made since the last save.
78After `describe` or `save`, `unrecorded` is empty; `in commit` still shows the
79work in the commit. Inspecting status does not save anything.
81To join an existing project instead, clone it and enter its directory:
83```
84j clone URL project
85cd project
86j status
87```
89---
91## Part 2 — The one idea everything rests on
93Here is the single most important thing to understand about `j`:
95> **If your expression evaluates to a function, that function is applied to
96> the repository. There is no `repo` variable — the repository is the implicit
97> argument.**
99`new`, `describe`, `prev`, `squash` are all functions `Repo -> Repo` (called
100**edits**). When you run `j squash`, you're not invoking a subcommand; you're
101evaluating the name `squash` to a function, which `j` then applies to your
102repository.
104This is why composition works. `.` is ordinary function composition, `(f . g)
105x = f (g x)` — **right to left**:
107```
108j 'describe "wip" . new'
109```
111reads as "first `new`, then `describe`," because `(describe "wip" . new) repo =
112describe "wip" (new repo)`. Run it and you've opened a new commit and set its
113message, in one expression, as one recorded operation.
115And it's why *inspections* are just functions too. `length . commits . top`
116composes three functions into one `Repo -> Int`:
118```
119j 'length . commits . top'
120 => 5
121```
123- `top` moves the focus to the top of history,
124- `commits` lists every commit of the (sub)tree,
125- `length` counts them.
127Since the result is an `Int` (not a `Repo`), `j` prints it and **persists
128nothing**. That's the rule: a `Repo` result persists; anything else prints.
130> **Two outcomes.** A run either **persists** (the result is a `Repo` that
131> differs from the loaded one) or **prints** (any other value). Edits persist;
132> inspections print.
134### A subtlety that trips people up
136`j 'show (length . commits . top)'` does *not* print `5`. It prints the
137function:
139```
140(.) ((length)) (((.) ((commits)) ((top))))
141```
143Why? `show (...)` applies `show` to the *function value* `length . commits .
144top`, producing a `Text` *without ever applying it to the repository*. To get
145the count, you want the composition itself applied: `length . commits . top`
146(with no `show`). The result is a function `Repo -> Int`, `j` applies it, and
147prints `5`. `show` is for rendering a *value*; here you want the value the
148function produces, so don't wrap it.
150---
152## Part 3 — Reading and moving around
154Make a few commits to play with:
156```
157j new; echo a > a.txt; j 'describe "A"'
158j new; echo b > b.txt; j 'describe "B"'
159j new; echo c > c.txt; j 'describe "C"'
160```
162Navigation changes the working-copy commit and checks out its files. Use
163these commands to edit another mutable commit:
165```
166j 'prev' -- to the parent
167j 'next' -- to the only child
168j 'tip' -- to the end of the current line
169j 'by @vqok' -- to a specific commit, by id (use a real id from j tree)
170```
172At a fork, choose an id from `j tree` with `j 'by @id'`; `next` only works
173when there is one child. Main and the root commit are protected from editing.
174Use `j 'new . goto trunk'` to start work on main, or
175`j 'tree . goto trunk'` to inspect it without moving your working copy.
176In a new local repository with no main bookmark, keep working from `j init`'s
177commit instead.
179After `j 'prev'` from C, the tree shows the focus (▶ and ◉) on B:
181```
182 ⌂ zzzz
183 └─ ● qmwp ▂
184 └─ ● nnvs ▂ A
185▶ └─ ◉ vqok ▂ B
186 └─ ◌ lttm C
187```
189To see the focus's data, use the inspection functions:
191```
192j 'files' -- the focus's files
193j 'changed' -- paths that differ from the parent
194j 'status' -- focus, changes in the commit, unrecorded edits, conflicts
195j 'log' -- a table of every commit, ✓ on the focus
196j 'review' -- the focus's changes as diffs (difftastic)
197```
199These inspections include current file edits without recording them. Use
200`j save` to record file edits on their own (`id` is the same edit). `status` spells out empty states
201such as `No changes` and `No conflicts`; `log` shows label names and short
202ids and adapts to narrow terminals.
204### Composition order, made concrete
206```
207j 'describe "edited two back" . prev'
208```
210`prev` runs *first* (right-to-left), then `describe`. From C, this moves to B
211and renames **B**. The focus ends on B:
213```
214 ⌂ zzzz
215 └─ ● qmwp ▂
216 └─ ● nnvs ▂ A
217▶ └─ ◉ vqok ▂ edited two back
218 └─ ◌ lttm C
219```
221If you meant "rename C," that's just `j 'describe "edited two back"'` — no
222`prev`. The lesson: read `.` chains from right to left, and remember each step
223operates on the repository the previous step produced.
225---
227## Part 4 — Dry runs: seeing before doing
229Use `preview` to validate an edit and show its resulting tree without saving:
231```
232j 'preview squash'
233```
235From the A → B history, this *shows* the tree as `squash` would leave it (B
236folded into A, its message gone):
238```
239 ⌂ zzzz
240 └─ ● krly ▂
241 └─ ● xspk ▂ A
242▶ └─ ◌ zkyl
243```
245But run `j 'tree'` right after and B is still there — **nothing persisted**,
246because `tree` returns a `Text`, not a `Repo`:
248```
249 ⌂ zzzz
250 └─ ● krly ▂
251 └─ ● xspk ▂ A
252▶ └─ ◌ nknz B
253```
255`preview` combines `tree` and `validate`. For an edit that takes arguments,
256put that edit in parentheses:
258```
259j 'preview (rebase %main)'
260```
262This renders the post-rebase tree **and** crashes (without touching anything)
263if that rebase couldn't actually happen — for instance, if `%main` doesn't
264exist yet. That's an *honest* dry run.
266The longer form is `j 'tree . validate . rebase %main'`. `validate` alone
267does not make an edit a dry run: `j 'validate . squash'` returns a repository
268and saves it. The final inspection (`tree`) is what prevents persistence.
270---
272## Part 5 — `or`: the whole error-handling model
274There are no exceptions to declare and no `Maybe`. A failing operation
275**crashes**, and `or` catches it:
277```
278head [] or 42 => 42
279edit or id -- run edit; if it crashes, run id (do nothing)
280```
282`id` is the identity edit, so `edit or id` means "try the edit, keep the
283repository if it fails."
285The reason this works for edits is **lifting**. A function value can't crash
286until it's applied, so `squash or id` would catch nothing if `or` simply
287returned the function. Instead, when both sides are functions, `or` builds a
288new one pointwise: `(f or g) x = f x or g x`. So:
290```
291j 'squash or id'
292```
294actually runs `squash` on your repository, and only if that crashes does it
295fall back to `id`. Try it on the A → B history: B gets folded into A, no crash,
296so `id` never runs.
298A practical use: "rebase onto main if there is a main, otherwise leave things
299alone":
301```
302j 'rebase %main or id'
303```
305---
307## Part 6 — Editing history for real
309Now that you can navigate, dry-run, and recover, the editing commands are just
310edits with clear semantics:
312```
313j 'describe "…"' -- set the focus's message
314j squash -- fold the focus into its parent
315j abandon -- drop the focus; children join the parent
316j 'split (ext "rs")' -- push the .rs files up into a new child commit
317j 'contract everything' -- push all of the focus's files into the parent
318j 'rebase %main' -- move the focused subtree onto main
319j 'pick @wqzt' -- copy @wqzt's change here as a new commit
320j 'backout @wqzt' -- a new commit undoing @wqzt's change
321```
323Two things make these safe to use freely:
3251. **The change id survives.** A commit keeps its `@wqzt…` id across
326 `describe`, `squash`, `rebase`. You're never tracking a moving sha.
3272. **Saved operations are undoable.** Use `j undo` and `j redo` to walk them.
329```
330j undo -- revert the last thing
331j redo -- reapply it
332j ops -- see the operation log
333```
335Undo and redo stop when files have unrecorded edits. Recording those edits
336with `j id` adds an operation, so the next undo removes that recording;
337another undo reaches the preceding edit. Recording also clears pending
338redos. Read `j ops` to choose how far to go back. Successful interactive
339undo/redo messages identify the affected operation and restored focus.
341### Resolve a conflict
343A successful rebase can leave conflicts in the working-copy commit. The edit
344is saved; there is no separate continuation command. Resolve it in four steps:
3461. Run `j status` to find conflicted paths, and `j review` to inspect changes.
3472. Edit those files, choosing the intended content and removing conflict markers.
3483. Run `j save` to record the resolution.
3494. Run `j status` again and check for `No conflicts` and `All edits recorded`.
351`j conflicts` lists conflicted commits throughout the repository. If a
352conflict is on another mutable commit, use its id from `j tree` with
353`j 'by @id'`, then follow the same steps. Run `j conflicts` again to check
354the whole history. `review` uses difftastic when
355installed and falls back to a unified diff when it is unavailable.
357### `split` and `contract`: moving changes by fileset
359A **fileset** is a function `Path -> Bool`. These two are inverses:
361- `split m` pushes the files matching `m` **up** into a new child commit.
362- `contract m` pushes the files matching `m` **down** into the parent.
364Suppose one commit touches `a.rs` and `b.j`, and you want the Rust change on
365its own:
367```
368j 'split (ext "rs")'
369```
371The `.rs` change becomes a separate child commit (now the focus); the `.j`
372change stays in the original. `squash` is defined in terms of these:
373`squash = abandon . contract everything`.
375Filesets compose: `under ./src`, `ext "rs"`, `both`, `either`, `neg`,
376`everything`. So `split (both (under ./src) (ext "rs"))` splits off only the
377Rust files under `src/`.
379---
381## Part 7 — Revsets: naming sets of commits
383A **revset** is a function `Repo -> [Id]` — a *set* of commits computed from
384the repository. You've been using them: `parents`, `kids`, `%main`.
386```
387here -- the focused commit
388parents -- its parents
389kids -- its children
390ancestors -- it and everything above
391descendants -- it and everything below
392all -- every visible commit
393trunk -- the main line (%main, %master, or %trunk)
394```
396Most commands that take a "which commits" argument take a revset. `goto` is
397the one place a revset must name *exactly one* commit:
399```
400j 'new . goto %main' -- select one main commit and start a mutable child
401```
403A missing label reports how to fetch and inspect remote labels. An ambiguous
404selector lists candidate ids and descriptions so you can choose with `by`.
406Revsets combine like sets:
408```
409j 'matching (\c -> c.message == "wip") all'
410```
412`matching p rs` keeps the commits of `rs` satisfying predicate `p` — here,
413every commit whose message is `wip`. The result is a list of commits, so it
414prints (persisting nothing).
416---
418## Part 8 — Batch operations with `at` and `forEach`
420`at rs e` runs edit `e` at the commit revset `rs` names, then returns the
421focus to where it was (by id, so `e` may move things):
423```
424j 'at parents (describe "parent edit")'
425```
427This renamed the *parent* while leaving you focused where you were — the tree
428shows the parent with its new message and the focus marker still on your
429original commit.
431`forEach rs e` runs `e` at *every* commit of `rs`:
433```
434j 'forEach descendants (describe "wip")'
435```
437Every commit below the focus is now described `wip`. `forEach` is not special
438— it's an ordinary function `Revset -> Edit -> Edit` defined in `config.j`,
439and you could have written it yourself.
441---
443## Part 9 — Making it yours: writing a command
445Everything `j` knows is defined in `config.j`. Any definition whose value is
446an `Edit` is a new command. Add this to your `~/.config/j/config.j`:
448```
449-- squash into the parent and name the combined commit "release"
450release : Edit
451release = describe "release" . prev . squash
452```
454Now `j release` works exactly like `squash` or `new`. The signature (`: Edit`)
455is optional but recommended — it makes `j` check the definition's arguments
456and result at each call.
458A revset example — "commits ahead of trunk":
460```
461ahead : Revset
462ahead = minus descendants trunk
463```
465Then `j 'length . ahead'` prints how many commits are on your branch but not
466the main line.
468This is the payoff of the whole design: the vocabulary is not a fixed list of
469subcommands. It's functions, in a file you can read and extend. `squash`,
470`tree`, `status`, `rebase` are definitions, not builtins.
472---
474## Part 10 — A realistic session, start to finish
476Putting it all together. Start a feature on top of main, commit in two parts,
477tidy the history, and push.
479This session assumes you have a remote with a `main` bookmark (so `trunk`
480resolves to something) — for example after `j clone`, or `j remote URL`
481followed by `j fetch`. `goto trunk` crashes if there's no main line yet;
482`new . goto trunk` is how you start *on top of* it (trunk is immutable, so you
483always open a new commit rather than focusing it directly).
485```
486# start on the main line
487j 'new . goto trunk'
489# first part
490echo 'fn parse() {}' > parser.rs
491j 'describe "add the parser"'
493# second part
494j 'new'
495echo 'mod parser;' > lib.rs
496j 'describe "wire up the parser"'
498# look at it
499j 'tree'
501# the second commit was really part of the first; fold it in
502j squash
504# squash leaves an empty child focused; move to the combined commit
505j prev
507# rename the combined commit
508j 'describe "add the parser"'
510# preview the rebase, then do it
511j 'preview (rebase %main)'
512j 'rebase %main'
514# share it
515j 'push (label "parser" here)'
516```
518Push sends recorded commits. If you edit files after the last saved edit,
519push warns that those edits are omitted. Run `j save`, then push again to
520include them. Looking at `j status` does not save the edits.
522And if any step goes wrong:
524```
525j undo
526```
528---
530## Where next
532- **[docs/language.md](language.md)** — the language in full: values, syntax,
533 semantics, contracts, `show`.
534- **[docs/base.md](base.md)** — every builtin and `config.j` definition, with
535 examples and an alphabetical index.
536- **[docs/comparison.md](comparison.md)** — how `j` maps onto git and jj, if
537 you're coming from either.
538- **[config.j](../config.j)** — the reference vocabulary, to read and imitate.