Where you create a new commit.
Then you start a new branch line. Next, you insert commits right in the middle of existing branches.
From there, you change the message of a commit down in the history tree.
And finally, you revert all you did.
It doesn't sound trivial, does it?
Describe + New + Abandon
$ jj log
@ x
◆ z � empty
First, let's give x a description (a message, in Git's lingo). You
can do this with the command jj description or jj desc for
short. Let's go:
$ jj desc -m "Initial commit"
Working copy (@) now at: xmvkoxku 45467cdf Initial commit
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
$ jj log
@ x Initial commit
◆ z � empty
Yes, it's another Git amend under the hood. Can we start ignoring this fact, already? Deal, I won't repeat it anymore.
New Change
How to create a new commit?
Remember the basic commands:
| Operation | Command |
|---|---|
| Create | new |
| Read | show |
| Update | edit |
| Delete | abandon |
| Move a Change somewhere else | rebase |
| Move the changes you made somewhere else | squash |
| Move files somewhere else | restore |
| Display the history tree | log |
| List / undo / redo operations | op log / undo / redo |
I bet you guessed: you need jj new.
$ jj new -m "A license"
Working copy (@) now at: xpsqkswo 6b9aeffd (empty) A license
Parent commit (@-) : xmvkoxku 45467cdf Initial commit
$ jj log
@ xp empty A license
○ xm Initial commit
◆ z empty
This creates a new, empty Change with the specified message. See
it's marked with @? It means that xp is now the Current Change,
and that you are editing it.
In Jujutsu it's idiomatic (but not mandatory) to commit before coding.
Indeed, you committed A license before actually adding the license:
the Change is still empty. More on this workflow later.
Providing a description is optional. You can always create a
messageless Change and add a message later with jj desc.
Time to add the license file:
$ echo "This software is open source" > LICENSE
You can trust it's already stored in the commit. Ready for your next Change:
$ jj new -m "A dummy React.js application"
$ jj log
@ o empty A dummy React.js application
○ t A license
○ x Initial commit
◆ z � empty
You get the point. Although this is a possible workflow, it's not the most efficient one. I recommend other ones. You'll learn more in a few pages.
Deleting Changes
How to delete a Change? Check the table above, you can't go wrong.
Solution
Solution
Of course! With abandon.
With jj abandon -r <CHANGE-ID> you can delete any Change you like,
no matter where it's located in the history tree.
Let's get rid of the Change introducing the license:
Solution
Solution
$ jj abandon -r t
Abandoned 1 commits:
tputwmoo c2541632 A license
Rebased 1 descendant commits onto parents of abandoned commits
Working copy (@) now at: otsrwyym ecb4d95f (empty) A dummy React.js application
Parent commit (@-) : xmvkoxku 45467cdf Initial commit
Added 0 files, modified 0 files, removed 1 files
$ jj log
@ o empty A dummy React.js application
○ x Initial commit
◆ z � empty
Check on the filesystem: the license is gone.
Hint: as we said, when there's no ambiguity, you can use positional
arguments, so jj abandon t would work as well.
Notice that you manipulated a change while you weren't there. This happens every time with Jujutsu, and it's very powerful. We will talk about it in On Not Being There.
Resurrecting Changes
Did you change your mind? Want to resurrect the license commit?
Solution
Solution
Yes! Undoing things is always jj undo:
$ jj undo
Undid operation: 3a6c93cd543e (2026-06-10 15:48:34) abandon commit c25416325e097a36d0cc1216fb4938e0c44b28a8
Restored to operation: 579bbd1014a4 (2026-06-10 15:41:13) abandon commit 855f900edf5df7275f3b666a7c7c8b64f755c0ed
Working copy (@) now at: otsrwyym 9c73504c (empty) A dummy React.js application
Parent commit (@-) : tputwmoo c2541632 A license
Added 1 files, modified 0 files, removed 0 files
$ jj log
@ o empty A dummy React.js application
○ t A license
○ x Initial commit
◆ z empty
Here we go, back to life!
Adding Changes In Arbitrary Positions
jj new adds a new Change on top of the Current Change. But you
can add Changes in arbitrary positions. Just select the Change you
want to start from, with -r, the same option you used with
abandon:
$ jj new -r x -m "One here"
@ k empty One here
│ ○ o empty A dummy React.js application
│ ○ t A license
├─╯
○ x Initial commit
◆ z empty
Of course, this started a new branch, which you can grow with other
jj new commands.
What if you want to insert a Change right before o (so between
o and t)? Use --before:
jj new --before o -m "One there"
○ o empty A dummy React.js application
@ zv empty One there
○ t A license
│ ○ k empty One here
├─╯
○ x Initial commit
◆ zz empty
You can shorten --before with -B.
Everything Is Dual
Symmetrically, there's also --after / -A. Insert a commit right after
zz:
Solution
Solution
jj new -A zz -m "Initial commit is no more"
○ o empty A dummy React.js application
○ zv empty One there
○ t A license
│ ○ k empty One here
├─╯
○ x Initial commit
@ m empty Initial commit is no more
◆ zz empty
So, in summary:
| Option | Meaning | From | To |
|---|---|---|---|
-r R | Make R its parent. | o-o-R-o-o | o-o-R-o-o \ X |
--after R | Add after R,between R and its children. | o-o-R-o-o | o-o-R-X-o-o |
--before T | Add before T,between T and its parents. | o-o-R-o-o | o-o-X-R-o-o |
Notice: all those moves are perfectly possible with Git. After all, Jujutsu is operating on a Git repository. They just happen to be more cumbersome, since you would need to think about the underlying necessary moves. So, it's likely you have rarely performed them.
The Ubiquitous -r Option
x's message is Initial commit, but now we inserted a Change
before it.
○ o empty A dummy React.js application
○ zv empty One there
○ t A license
│ ○ k empty One here
├─╯
○ x Initial commit
@ m empty Initial commit is no more
◆ zz empty
It's mildly infuriating! How to amend its message?
Here's something I like in Jujutsu: once you have learnt an element of
its grammar, you can use it everywhere it makes sense. -r can be
used with jj new to reference a Change. It can be used with log,
with show and with many other commands.
Including jj desc. So:
Solution
Solution
jj desc -r x -m "Hello, world"
○ o empty A dummy React.js application
○ zv empty One there
○ t A license
│ ○ k empty One here
├─╯
○ x Hello, world
@ m empty Initial commit is no more
◆ zz empty
Fixed. Good job.
What's The Point of Creating Empty Commits?
You are right, these were silly exercises. Who would ever create empty commits like this? Yet, notice:
- Nothing prevents you to
jj editthose commits and add actual content as a second step. - As you'll see, committing before coding is really idiomatic in Jujutsu. There are workflows based on that.
- Finally: editing Changes is not the only way to populate them. You will find yourself moving changes and files around, and you will find that empty commits are a useful tool.
Cleaning up
Here's the chapter closing exercise. Move to o:
jj edit o
Now, get rid of these dummy commits you just created:
k: One herezv: One therem: Initial commit is no more
How would you do that?
Solution
Solution
Of course, combining abandon and -r!
$ jj abandon -r k -r zv -r m
@ o empty A dummy React.js application
○ t A license
○ x Hello, world
◆ z empty
Congrats if you got it!
Indeed, many commands operate on one or multiple Changes, whether
contiguous or sparse.
The following are equivalent:
jj abandon -r k -r zv -r mjj abandon -r 'k | zv | m'jj abandon k zv m
jj abandon k zv m works because Jujutsu allows passing revsets as positional arguments when nothing else could be meant. Commands that
take filesets are a notable exception.
The last one uses the | operator of the Revset
Language, a very powerful
and yet simple language to select sets of Changes. You'll read a bit
more about it in A Glimpse of the Revset Language.
Where To Go From Here?
Admittedly, those exercises were a bit contrived, weren't they? We needed them to learn the basic moves of Jujutsu. Make them your own, and in the next few pages we will see how to apply them to genuinely useful use cases.
In the meantime, be proud! You just made some manipulations that are far from trivial, if just using bare, raw Git.