Resurrecting And Branching
Important
You play with undoing operations, start new development lines by inserting Changes mid-branch, and edit messages deep in the history tree. It’s easier than it sounds!
Warning
Changed your mind? Want to resurrect the license commit?
Tip
Yes! Undoing is always
jj undo:jj undoUndid operation: 3a6c93cd543e (2026-06-10 15:48:34) abandon commit c25416325e097a36d0cc1216fb4938e0c44b28a8@ o empty A dummy React.js application ○ t A license ○ x Initial commit ◆ z empty
Back to life!
Adding Changes In Arbitrary Positions
jj new adds a Change on top of the Current Change. But you can
insert Changes anywhere. Select where using -r (the same option used
with abandon):
jj new -r x -m "One here"
@ k empty One here <-- this one
│ ○ o empty A dummy React.js application
│ ○ t A license
├─╯
○ x Initial commit
◆ z empty
This started a new branch line, which you can grow with more jj new
commands.
Want to insert a Change right before o (between o and t)? Use
--before (or -B):
new --before o -m "One there"
○ o empty A dummy React.js application
@ zv empty One there <-- this one
○ t A license
│ ○ k empty One here
├─╯
○ x Initial commit
◆ zz empty
Everything Is Dual
Symmetrically, there’s also --after (or -A).
Warning
Insert a commit right after
zz.
Tip
jj new -A zz -m "Initial commit is no more"~ ○ x Initial commit @ m empty Initial commit is no more ◆ zz empty
In summary, starting from:
o-o-R-o-o
This is what you get with the 3 different options:
jj new -r R
o-o-R-o-o
\
X
jj new --after R
o-o-R-X-o-o
jj new --before R
o-o-X-R-o-o
These moves are perfectly possible in Git, but cumbersome because you must handle the underlying mechanics. So, it’s unlikely you have performed them often.
The Ubiquitous -r Option
x’s message is Initial commit:
○ x Initial commit <-- this one
@ m empty Initial commit is no more
◆ zz empty
Having inserted a Change before it, the description is now misleading. Mildly infuriating! How to amend it?
You’ll love this about Jujutsu: once you learn a grammar element, you
can use it everywhere it makes sense. -r can be used with jj new
to position a Change, with log to define what to display, with jj desc to target a message…
Warning
How to fix
x’s description?
Tip
jj desc -r x -m "Hello, world"○ x Hello, world @ m empty Initial commit is no more ◆ zz emptyFixed. Good job.
What’s The Point of Creating Empty Commits?
Who would ever create empty commits, though? Notice:
- You can
jj editthem later and add actual content. - Committing before coding is idiomatic in Jujutsu workflows.
- Editing isn’t the only way to populate Changes. You’ll soon move edits and files from a distance, and empty commits act as useful placeholders.
Cleaning up
Here’s a Chapter closing exercise. Move to o:
jj edit o
Get rid of the dummy commits you just created:
k: One herezv: One therem: Initial commit is no more
Warning
How would you do that?
Tip
Combine
abandonand-r:jj abandon -r k -r zv -r m@ o empty A dummy React.js application ○ t A license ○ x Hello, world ◆ z emptyCongrats if you got it!
Many commands operate on single or multiple Changes, contiguous or sparse. These are equivalent:
jj abandon -r k -r zv -r mjj abandon k zv mjj abandon -r 'k | zv | m'
jj abandon k zv m works because when there is no ambiguity, revsets
can be passed as positional arguments.
The last example uses the | operator from the Revset
Language, a powerful yet
simple syntax for selecting Change sets. Read more in A Glimpse of
the Revset Language.
Where To Go From Here?
Admittedly, these exercises were a bit contrived. Make them your own, and we will soon apply them to genuinely useful cases.
In the meantime, be proud! You just executed manipulations that are far from trivial in raw Git.