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/
- @ - the current revision
- x- - the parent(s) of some revision x ("the revision before it" in the simple case)
- y+ - the child(ren) of y ("the revision after it" in the simple case)
- z:: - all descendants of z, including z
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