Charlotte's Jujutsu Tutorial

olexsmir1 pts0 comments

charlotte's jujutsu tutorial - Charlotte Som

okay. you know already know what jj (jujutsu for long) is, right? like, vaguely? it’s like a<br>git but it’s compatible with git and your most annoying oomfs all love it and you always say you’ve been meaning to<br>give it a try some day but like you aren’t really pressed because git is like, good enough…? that one. let me teach you how to become one of those<br>annoying oomfs. you’re gonna love it.

getting started

step one is you install jj. i literally get it from nixpkgs and winget which covers linux mac and windows; jj is probably in<br>your system’s1 package manager as well.

step two is you use jj. i will teach you how to use jj in just a second.

the jj mental model

at a super high level, as a git-compatible version control system, jj tracks a bunch of commits. in jj, each commit is part of a ‘revision’ which has<br>a stable change ID , separate to its commit ID: jj gives each commit a “visible” flag, and gives you the illusion of few<br>mutable revisions by creating many immutable commits under the same change IDs, marking the present version hidden and the new version visible, while<br>automatically rebasing child and descendant commits to the new version.

that’s pretty abstract, so let’s take a look at how jj presents revisions to us:

watch-party/ (jj: rsku) $ jj log<br>@ rskulwum charlotte@som.codes 2026-05-13 00:32:36 001afa51<br>│ (empty) (no description set)<br>◆ mxklzpnz charlotte@som.codes 2026-05-12 14:02:50 main fb45c965<br>│ tweak the sounds a little<br>◆ lloylxvy charlotte@som.codes 2026-05-12 10:33:29 81ed6047<br>│ add deno task for formatting and run it<br>◆ znlzuqxr charlotte@som.codes 2026-05-12 10:33:29 e0d55474<br>│ add readycheck command with notification sounds

you can see both IDs in action here - each revision has a change ID in jj’s “reverse-hex notation” (it uses the last 16 letters of the<br>alphabet!) as well as a disambiguating commit ID in regular hex notation (1:1 with git) to distinguish two commits with the same change<br>ID (e.g. different iterations of the same revision).

the other thing you can note is the green @ indicating that we’re currently checked out on an empty descriptionless commit - this is the<br>working-copy commit: instead of having a staging area that you git add files to, jj automatically tracks all workdir changes<br>whenever it’s invoked and makes you “edit” a commit. this is both simpler and more powerful2: jj unifies the experience of editing a brand new commit and editing any historical or parallel revision, which makes working with branching history<br>or rewriting past history easy. at any time, you can run jj edit to move your workdir to a specific revision, and then<br>directly make your changes on the working copy.

baby’s first commit

now that we’ve built a mental model of jj (malleable revisions made of immutable commits, plus a working-copy commit),<br>let’s get more concrete.

you can type jj git init in an empty directory to create a new git-backed jj repo (à la git init), or in an<br>existing git repository to convert it to a git-backed jj repo. by default, these repos have git colocation, which means you can<br>use both git commands and jj commands to operate on them.

to begin, we’ll set our user.name and user.email, just like git:

$ jj config set --user user.name "My Name"<br>$ jj config set --user user.email "me@my-site.com"

we first create an empty repository, and see we’re on an empty working-copy commit:

$ mkdir hello-jj && cd hello-jj/<br>hello-jj/ $ jj git init<br>hello-jj/ (jj: rkxs) $ jj log<br>@ rkxsqzky charlotte@som.codes 2026-07-13 10:57:55 2f832520<br>│ (empty) (no description set)<br>◆ zzzzzzzz root() 00000000

then we can write a conversation to a file, and take a look again. that second line will also give jj a stable diff anchor when we edit history in a<br>couple steps.

hello-jj/ (jj: rkxs) $ echo 'A: meow!' > conversation.txt<br>hello-jj/ (jj: rkxs) $ echo 'B: what?' >> conversation.txt<br>hello-jj/ (jj: rkxs) $ jj log<br>@ rkxsqzky charlotte@som.codes 2026-07-13 10:58:01 1e8c2f8b<br>│ (no description set)<br>◆ zzzzzzzz root() 00000000

you can see our commit ID has changed from 2f83 to 1e8c, but our revision is still rkxs. let’s give this commit<br>a description:

hello-jj/ (jj: rkxs) $ jj describe -m "say meow"<br>Working copy (@) now at: rkxsqzky 8ee550a9 say meow<br>Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)<br>hello-jj/ (jj: rkxs) $ jj log<br>@ rkxsqzky charlotte@som.codes 2026-07-13 10:58:06 8ee550a9<br>│ say meow<br>◆ zzzzzzzz root() 00000000

baby’s second commit

now we can work on top of this revision. let’s add some more content to conversation.txt:

hello-jj/ (jj: rkxs) $ jj new<br>Working copy (@) now at: ztklkwuv 67bf7c3a (empty) (no description set)<br>Parent commit (@-) : rkxsqzky 8ee550a9 say meow<br>hello-jj/ (jj: ztkl) $ echo 'A: i mean, hello.' >> conversation.txt<br>hello-jj/ (jj: ztkl) $ cat conversation.txt<br>A: meow!<br>B: what?<br>A: i mean, hello.<br>hello-jj/ (jj: ztkl) $ jj describe -m "say hello as well"<br>[…]<br>hello-jj/ (jj: ztkl) $ jj log<br>@ ztklkwuv...

hello commit charlotte empty rkxs revision

Related Articles