Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Goal

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:

OperationCommand
Createnew
Readshow
Updateedit
Deleteabandon
Move a Change somewhere elserebase
Move the changes you made somewhere elsesquash
Move files somewhere elserestore
Display the history treelog
List / undo / redo operationsop 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

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

$ 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

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

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:

OptionMeaningFromTo
-r RMake R its parent.o-o-R-o-o


o-o-R-o-o
     \
      X
--after RAdd after R,
between R and its children.
o-o-R-o-oo-o-R-X-o-o
--before TAdd before T,
between T and its parents.
o-o-R-o-oo-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

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 edit those 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 here
  • zv: One there
  • m: Initial commit is no more

How would you do that?

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 m
  • jj 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.