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`) and a
8`config.j` with your identity (see the README's Configuration section). Every
9command shown here is run in a real repository, so you can follow along.
13## Part 1 — Ten minutes to a working repository
15Make an empty directory and create a repository:
22`j init` makes a colocated `.jj`/`.git` repository and checks out a fresh
23working-copy commit. Now write a file and start your first change:
26echo 'fn main() {}' > main.rs
28j 'describe "start the project"'
40▶ └─ ◌ psnq start the project
43Add a second change on top:
48j 'describe "add a library"'
55 └─ ● psnq ▂ start the project
56▶ └─ ◌ trtq add a library
59That's the whole basic loop: **edit files, `new`, `describe`.** There is no
60`git add` step — the working directory is always already a commit, and `j`
61snapshots it on every run.
63**What just happened, in the model:** each `j` run snapshots your working
64directory into the working-copy commit, builds a `Repo` value focused there,
65evaluates your expression, and — because `new` and `describe` are *edits*
66(functions `Repo -> Repo`) — persists the resulting repository.
70## Part 2 — The one idea everything rests on
72Here is the single most important thing to understand about `j`:
74> **If your expression evaluates to a function, that function is applied to
75> the repository. There is no `repo` variable — the repository is the implicit
78`new`, `describe`, `prev`, `squash` are all functions `Repo -> Repo` (called
79**edits**). When you run `j squash`, you're not invoking a subcommand; you're
80evaluating the name `squash` to a function, which `j` then applies to your
83This is why composition works. `.` is ordinary function composition, `(f . g)
84x = f (g x)` — **right to left**:
87j 'describe "wip" . new'
90reads as "first `new`, then `describe`," because `(describe "wip" . new) repo =
91describe "wip" (new repo)`. Run it and you've opened a new commit and set its
92message, in one expression, as one recorded operation.
94And it's why *inspections* are just functions too. `length . commits . top`
95composes three functions into one `Repo -> Int`:
98j 'length . commits . top'
102- `top` moves the focus to the top of history,
103- `commits` lists every commit of the (sub)tree,
104- `length` counts them.
106Since the result is an `Int` (not a `Repo`), `j` prints it and **persists
107nothing**. That's the rule: a `Repo` result persists; anything else prints.
109> **Two outcomes.** A run either **persists** (the result is a `Repo` that
110> differs from the loaded one) or **prints** (any other value). Edits persist;
113### A subtlety that trips people up
115`j 'show (length . commits . top)'` does *not* print `5`. It prints the
119(.) ((length)) (((.) ((commits)) ((top))))
122Why? `show (...)` applies `show` to the *function value* `length . commits .
123top`, producing a `Text` *without ever applying it to the repository*. To get
124the count, you want the composition itself applied: `length . commits . top`
125(with no `show`). The result is a function `Repo -> Int`, `j` applies it, and
126prints `5`. `show` is for rendering a *value*; here you want the value the
127function produces, so don't wrap it.
131## Part 3 — Reading and moving around
133Make a few commits to play with:
136echo a > a.txt; j 'new'; j 'describe "A"'
137echo b > b.txt; j 'new'; j 'describe "B"'
138echo c > c.txt; j 'new'; j 'describe "C"'
141Navigation is edits too:
144j 'prev' -- to the parent
145j 'next' -- to the only child
146j 'top' -- to the top of history
147j 'tip' -- to the end of the current line
148j 'by @vqok' -- to a specific commit, by id (use a real id from j tree)
149j 'goto %main' -- to what the remote calls main
152After `j 'prev'` from C, the tree shows the focus (▶ and ◉) on B:
162To see the focus's data, use the inspection functions:
165j 'files' -- the focus's files
166j 'changed' -- paths that differ from the parent
167j 'status' -- id, message, labels, changed, conflicts
168j 'log' -- a table of every commit, ✓ on the focus
169j 'review' -- the focus's changes as diffs (difftastic)
172### Composition order, made concrete
175j 'describe "edited two back" . prev'
178`prev` runs *first* (right-to-left), then `describe`. From C, this moves to B
179and renames **B**. The focus ends on B:
185▶ └─ ◉ vqok ▂ edited two back
189If you meant "rename C," that's just `j 'describe "edited two back"'` — no
190`prev`. The lesson: read `.` chains from right to left, and remember each step
191operates on the repository the previous step produced.
195## Part 4 — Dry runs: seeing before doing
197Because inspections persist nothing, you can preview any edit by composing it
204From the A → B history, this *shows* the tree as `squash` would leave it (B
205folded into A, its message gone):
214But run `j 'tree'` right after and B is still there — **nothing persisted**,
215because `tree` returns a `Text`, not a `Repo`:
224For a stronger guarantee, insert `validate`. `validate repo` returns the repo
225unchanged if persisting would succeed, and crashes with the exact error
229j 'tree . validate . rebase %main'
232This renders the post-rebase tree **and** crashes (without touching anything)
233if that rebase couldn't actually happen — for instance, if `%main` doesn't
234exist yet. That's an *honest* dry run.
238## Part 5 — `or`: the whole error-handling model
240There are no exceptions to declare and no `Maybe`. A failing operation
241**crashes**, and `or` catches it:
245edit or id -- run edit; if it crashes, run id (do nothing)
248`id` is the identity edit, so `edit or id` means "try the edit, keep the
249repository if it fails."
251The reason this works for edits is **lifting**. A function value can't crash
252until it's applied, so `squash or id` would catch nothing if `or` simply
253returned the function. Instead, when both sides are functions, `or` builds a
254new one pointwise: `(f or g) x = f x or g x`. So:
260actually runs `squash` on your repository, and only if that crashes does it
261fall back to `id`. Try it on the A → B history: B gets folded into A, no crash,
264A practical use: "rebase onto main if there is a main, otherwise leave things
268j 'rebase %main or id'
273## Part 6 — Editing history for real
275Now that you can navigate, dry-run, and recover, the editing commands are just
276edits with clear semantics:
279j 'describe "…"' -- set the focus's message
280j squash -- fold the focus into its parent
281j abandon -- drop the focus; children join the parent
282j 'split (ext "rs")' -- push the .rs files up into a new child commit
283j 'contract everything' -- push all of the focus's files into the parent
284j 'rebase %main' -- move the focused subtree onto main
285j 'pick @wqzt' -- copy @wqzt's change here as a new commit
286j 'backout @wqzt' -- a new commit undoing @wqzt's change
289Two things make these safe to use freely:
2911. **The change id survives.** A commit keeps its `@wqzt…` id across
292 `describe`, `squash`, `rebase`. You're never tracking a moving sha.
2932. **`j undo` always works.** Every operation is recorded.
296j undo -- revert the last thing
298j ops -- see the operation log
301### `split` and `contract`: moving changes by fileset
303A **fileset** is a function `Path -> Bool`. These two are inverses:
305- `split m` pushes the files matching `m` **up** into a new child commit.
306- `contract m` pushes the files matching `m` **down** into the parent.
308Suppose one commit touches `a.rs` and `b.j`, and you want the Rust change on
315The `.rs` change becomes a separate child commit (now the focus); the `.j`
316change stays in the original. `squash` is defined in terms of these:
317`squash = abandon . contract everything`.
319Filesets compose: `under ./src`, `ext "rs"`, `both`, `either`, `neg`,
320`everything`. So `split (both (under ./src) (ext "rs"))` splits off only the
321Rust files under `src/`.
325## Part 7 — Revsets: naming sets of commits
327A **revset** is a function `Repo -> [Id]` — a *set* of commits computed from
328the repository. You've been using them: `parents`, `kids`, `%main`.
331here -- the focused commit
332parents -- its parents
334ancestors -- it and everything above
335descendants -- it and everything below
336all -- every visible commit
337trunk -- the main line (%main, %master, or %trunk)
340Most commands that take a "which commits" argument take a revset. `goto` is
341the one place a revset must name *exactly one* commit:
344j 'goto %main' -- crash if %main names 0 or 2+ commits
347Revsets combine like sets:
350j 'matching (\c -> c.message == "wip") all'
353`matching p rs` keeps the commits of `rs` satisfying predicate `p` — here,
354every commit whose message is `wip`. The result is a list of commits, so it
355prints (persisting nothing).
359## Part 8 — Batch operations with `at` and `forEach`
361`at rs e` runs edit `e` at the commit revset `rs` names, then returns the
362focus to where it was (by id, so `e` may move things):
365j 'at parents (describe "parent edit")'
368This renamed the *parent* while leaving you focused where you were — the tree
369shows the parent with its new message and the focus marker still on your
372`forEach rs e` runs `e` at *every* commit of `rs`:
375j 'forEach descendants (describe "wip")'
378Every commit below the focus is now described `wip`. `forEach` is not special
379— it's an ordinary function `Revset -> Edit -> Edit` defined in `config.j`,
380and you could have written it yourself.
384## Part 9 — Making it yours: writing a command
386Everything `j` knows is defined in `config.j`. Any definition whose value is
387an `Edit` is a new command. Add this to your `~/.config/j/config.j`:
390-- fold the whole stack into one commit called "release"
392release = describe "release" . (contract everything)
395Now `j release` works exactly like `squash` or `new`. The signature (`: Edit`)
396is optional but recommended — it makes `j` check the definition's arguments
397and result at each call.
399A revset example — "commits ahead of trunk":
403ahead = minus descendants trunk
406Then `j 'length . ahead'` prints how many commits are on your branch but not
409This is the payoff of the whole design: the vocabulary is not a fixed list of
410subcommands. It's functions, in a file you can read and extend. `squash`,
411`tree`, `status`, `rebase` are definitions, not builtins.
415## Part 10 — A realistic session, start to finish
417Putting it all together. Start a feature on top of main, commit in two parts,
418tidy the history, and push.
420This session assumes you have a remote with a `main` bookmark (so `trunk`
421resolves to something) — for example after `j clone`, or `j remote URL`
422followed by `j fetch`. `goto trunk` crashes if there's no main line yet;
423`new . goto trunk` is how you start *on top of* it (trunk is immutable, so you
424always open a new commit rather than focusing it directly).
427# start on the main line
431echo 'fn parse() {}' > parser.rs
432j 'describe "add the parser"'
435echo 'mod parser;' > lib.rs
437j 'describe "wire up the parser"'
442# the second commit was really part of the first; fold it in
445# rename the combined commit
446j 'describe "add the parser"'
448# preview the rebase, then do it
449j 'tree . validate . rebase %main'
453j 'push (label "parser" here)'
456And if any step goes wrong:
466- **[docs/language.md](language.md)** — the language in full: values, syntax,
467 semantics, contracts, `show`.
468- **[docs/base.md](base.md)** — every builtin and `config.j` definition, with
469 examples and an alphabetical index.
470- **[docs/comparison.md](comparison.md)** — how `j` maps onto git and jj, if
471 you're coming from either.
472- **[config.j](../config.j)** — the reference vocabulary, to read and imitate.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list