kota's memex

https://github.com/martinvonz/jj

Jujutsu's command line interface has a much smaller surface area than Git, and its mental model is simpler. Yet it manages to be equally powerful. A very elegant tool.

guides

https://steveklabnik.github.io/jujutsu-tutorial/

https://maddie.wtf/posts/2025-07-21-jujutsu-for-busy-devs

gotchas

git clean

Running git clean deletes non-tracked files, including the .jj directory!

bookmarks do not move automatically

Bookmarks are a bit like branches, but they do not move to newer commits automatically. To move bookmarks use jj bookmark move.

gitignore

You can use .gitignore, but because files are automatically committed it's easy to accidentally commit a file and then later wish to ignore it.

jj file untrack path/to/file

undo

You can undo a destructive action with a simple:

jj op undo

You can also view all operations:

jj op log

And restore the repository to any earlier state in the log:

jj op restore <op id>

first time setup

Similar to git:

jj config set --user user.name "Your Name"
jj config set --user user.email "your.name@example.com"

init / clone repo

jj git init --colocate .
jj git clone --colocate git@git.sr.ht:~kota/kudoer

fetching changes

jj git fetch

navigation

selecting a revision

Many commands accept a <revset> which is a simple functional expression language inspired by mercurial: https://jj-vcs.github.io/jj/latest/revsets/

log

Show revision history.

jj log

# See everything on the main branch
jj log -r '::main'

# Main branch leading up to your current revision
jj log -r '::main | ::@'

# All your commits
jj log -r 'mine()'

# EVERYTHING
jj log -r ..

# View all commits on a "feature branch" starting from the last shared ancestor
with the main branch. This is the selection you'd get with `jj rebase -b @ -o main`:
jj log -r 'roots(main..feature)::'

view an old commit

Use new everywhere unless you really mean to change something in place. You can still edit the older commit with squash if you really need to, but using new will prevent you from accidentally making a change.

jj new <rev>

edit

Edit is used both the modify an existing commit, like git amend, but also for navigate. It simply sets the specified revision as the working-copy revision.

For an amend like situation or to simply view an older commit, it's usually a good idea to instead do jj new optionally followed by jj squash in case you change your mind.

jj edit <rev>

diff

Uses current revision by default.

jj diff

With the -r option, shows the changes compared to the parent revision. If there are several parent revisions (if the given revision is a merge), then they will be merged and the changes from the result to the given revision will be shown.

jj diff -r <rev>

With the --from and/or --to options, shows the difference from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, jj diff --from main shows the changes from "main" (perhaps a bookmark name) to the working-copy commit.

jj diff --from A --to D

You can also look at just the changes in specific files:

jj diff src/bin/**.rs

blame / credit

Show the source change for each line of the target file.

jj file annotate src/main.rs

making changes

new

Create a new and empty change.

jj new
jj new <rev>

You can create a revision after and before another which will insert it into the history rather than branching off on a new tangent. In other words using -A will make a new revision as a child of <rev>, but also as a parent of the revisions which were children of <rev>. Meaning the commit IDs of <rev>s children will change.

# Insert AFTER or BEFORE a revision
jj new -A <rev>
jj new -B <rev>

describe

Update the change description or other metadata.

jj describe

jj describe --reset-author

squash

Move changes from a revision into another revision. By default it squashes the current commit into its parent.

jj squash

Sometimes you want to "squash you entire branch", usually to rebase it onto another branch more easily (if you don't care about your branches history).

First, create a commit on the destination branch and then squash the entire other branch starting from the last shared ancestor between both branches:

jj new main
jj squash --from 'roots(main..feature)::'

split

Split a revision in two. Because there's no "working copy" this is how you move your current changes into a separate commit.

jj split

duplicate / cherry-pick

JJ calls cherry-picking "duplicating", which kinda makes more sense.

jj duplicate <source ID> --destination <ID of destination base rev>

Like the new command you can put the duplicated commit before or after another one in sequence:

jj duplicate <source ID> -A <rev to insert it after>
jj duplicate <source ID> -B <rev to insert it before>

rebase

This will move a and all of its children onto the destination, main. This is a bit like a git rebase --onto.

jj rebase --source a --destination main

Jujutsu can alternatively figure out which revisions you want it to move based on the tip of the branch rather than the root:

jj rebase --branch my-cool-feature --destination main
# You actually don't need to use a bookmark/branch name.

If no options are provided it defaults to --branch @:

jj rebase --destination main

You can also rebase into a series of revisions rather than at the end using the -A and -B options used in new.

conflicts

Conflicts may have been created during your rebase. You can just use jj edit to edit the conflicted revisions.

resolve

Jujutsu includes a subcommand called resolve which will open your default merge-editor with all the current merge conflicts.

merge

Merges are just revisions with multiple parents. You can use jj new with multiple arguments to merge a branch:

jj new b a

This order DOES matter. In the above example, b becomes the "first parent" of the new revision. This is the equivalent of git checkout b && git merge a.

Finally, you'll probably want to describe your merge:

jj describe

branches / bookmarks

JJ has a different relationship with branches than git. Instead, they're called bookmarks and there's no concept of "being on a branch". Rather you can think of them as just pointers that you can move around to point at a location in the tree of commits.

create and update a bookmark

If you have a revision containing a change that you'd like to use for a PR:

jj bookmark create add-readme-line -r <rev>

This command will create a new bookmark called add-readme-line pointing at the <rev> you specified. We can push it:

jj git push -b add-readme-line --allow-new

Later, after a review, you need to add a few more changes to your PR:

# Check where you are
jj log

# Create a new revision on top of the one you used earlier
jj new <earlier-rev>

# Make some changes

jj commit -m "fix typo in README.md"

jj bookmark move add-readme-line --to <new-rev>
jj git push

delete a bookmark

# Delete locally
jj bookmark delete <bookmark-name>

# Push out the deletion
jj git push --deleted

getting rid of changes

If you're in the middle of an edit on a revision and would like to "restore" a file to its original state in that revision (discard you changes):

jj restore path/to/file

# ALL CHANGES
jj restore

You can also get rid of all changes AND the revision itself:

jj abandon

You can restore the version of a file from a particular revision:

jj restore --from main path/to/file