Deleting commits and bookmarks
Reset your progress
Reset your progress
To reset your progress to the start of this chapter, run the following command:
curl https://jj-for-everyone.github.io/reset.sh | bash -s abandon
cd ~/jj-tutorial/repo
Remeber the for-loop experiment Alice had put aside for a while? Jujutsu let's you keep as many of those experiments around as you like. Let's simulate a few with these commands:
jj commit -m "Experiment: Migrate to shiny new framework"
jj git push --change @-
jj new main
jj commit -m "Experiment: Improve scalability using microservices"
jj git push --change @-
jj new main
jj commit -m "Experiment: Apply SOLID design patterns"
jj git push --change @-
jj new main
@ vqytywws alice@local 2025-08-31 14:29:56 9bdb2d1d │ (empty) (no description set) │ ○ oqsqtxwo alice@local 2025-08-31 14:29:56 push-oqsqtxwowvuw 2ffeb883 ├─╯ (empty) Experiment: Apply SOLID design patterns │ ○ lqpnzsmz alice@local 2025-08-31 14:29:56 push-lqpnzsmzkxry f4cf1d7f ├─╯ (empty) Experiment: Improve scalability using microservices │ ○ rppuwxxp alice@local 2025-08-31 14:29:56 push-rppuwxxpnkqu 8c44777f ├─╯ (empty) Experiment: Migrate to shiny new framework ◆ snnoxnvq alice@local 2025-08-31 14:29:56 main git_head() 346c0acd │ Merge repetition and translation of greeting ~
After a lengthy debate with Bob, Alice must admit that none of these experiments are good ideas. She decides to delete the commits and bookmarks.
The jj command to do that is jj abandon <change-id>
.
You can simply call this command three times with the change ID of each experimental commit.
You can also copy-paste the following command, which deletes them all at once:
jj abandon 'description("Experiment")'
Abandoned 3 commits: oqsqtxwo 2ffeb883 push-oqsqtxwowvuw | (empty) Experiment: Apply SOLID design patterns lqpnzsmz f4cf1d7f push-lqpnzsmzkxry | (empty) Experiment: Improve scalability using microservices rppuwxxp 8c44777f push-rppuwxxpnkqu | (empty) Experiment: Migrate to shiny new framework Deleted bookmarks: push-lqpnzsmzkxry, push-oqsqtxwowvuw, push-rppuwxxpnkqu Hint: Deleted bookmarks can be pushed by name or all at once with `jj git push --deleted`.
The output from jj abandon
tells us that all three commits were deleted, including the bookmarks that were pointing to them.
Jujutsu assumes that if you want to delete a commit, you probably don't need the bookmark pointing to it anymore either.
If you actually want to keep it, move the bookmark to a commit you're planning to preserve before running jj abandon
.
That being said, the bookmarks are only deleted locally.
Just like new or moved bookmarks, deleted ones have to be synchronized explicitly.
This makes it easier to fix mistakes.
If you accidentally delete a bookmark locally, you can jj undo
that without issues.
If you deleted it on the remote already, restoring it takes a bit more effort.
Let's sync the deleted bookmarks with the remote:
jj git push --deleted
Duplicate commits when using Git directly
Duplicate commits when using Git directly
We've talked about how Jujutsu is compatible with Git.
It creates a .git
directory in your repo that looks like it was made and managed by Git itself.
This let's other tools that interoperate with Git work seamlessly.
As a rule of thumb, this interoperability work flawlessly if other tools only read the .git
directory.
However, there can be slight issues if they modify it as well.
The fix is usually simple, but it helps to have an intuition of what can go wrong.
Let's make a simple example.
The following commands add some file to Jujutsu's working copy commit.
Afterwards, a new commit is created with Git itself.
Running jj log
shows a branched-off commit that seems out of place.
touch some_file
jj status # record new file into working copy
git commit -am "add some file" # create commit with git, bypassing jj
jj log
@ nwstlotv alice@local 2025-08-31 14:44:23 928b48f4 │ (empty) (no description set) ○ yqyzyzzo remo@buenzli.dev 2025-08-31 14:44:22 git_head() 30e0eec4 │ add some file │ ○ vqytywws alice@local 2025-08-31 14:44:05 70588b87 ├─╯ (no description set) ◆ snnoxnvq alice@local 2025-08-31 14:29:56 main 346c0acd │ Merge repetition and translation of greeting ~
This branched-off commit is actually the previous working copy commit of Jujutsu.
Jujutsu doesn't know that the commit created with Git is basically the same.
(If you jj show
both commits, you'll see that they contain the same file changes.)
The solution here is simply to jj abandon
the dangling commit, since it's a duplicate.
Probably 99% of problems caused by different tools modifying the Git database are just like this.
The next time you run Jujutsu, it will find new commits it doesn't know what to do with, so it keeps all of them.
You then have to delete the ones you don't need.
Let's clean up this whole tangent:
jj abandon main+ # abandon direct children of main
Immutability
This is a good place to talk about mutable and immutable commits.
jj abandon
is a command that modifies the commit history.
A commit represents something that happened.
By deleting a commit, you're basically saying "Nu-uh, that didn't happen."
Learning about jj rebase
was the first time we rewrote history, remember?
Or did you jj abandon
that memory, hihi?
Rewriting history is often a perfectly normal and logical thing to do.
But it can be dangerous!
For example, imagine what would happen if Alice rewrote commits on the main
branch.
That would be very confusing for Bob!
It would be equally confusing for Bob if Alice rewrote commits pointed to by Bob's bookmarks.
There are various complicated problems Alice and Bob can run into like this.
To avoid these problems, people who collaborate using version control systems usually follow a simple rule of thumb:
Never rewrite history on shared branches.
Usually that's just the main
branch, but there can be more, depending on the project.
On personal branches, things are usually more relaxed. Alice should expect Bob to rewrite history freely on his own branches. In return, she gets to rewrite history on hers. If you need to rely on the history of someone else's branch to remain stable, you should probably talk to them about that.
Now for the great news:
Jujutsu enforces these rules by default!
That way, you don't have to worry about them most of the time.
Try deleting the latest commit on the main
branch:
jj abandon main
Error: Commit 346c0acd36db is immutable Hint: Could not modify commit: snnoxnvq 346c0acd main | Merge repetition and translation of greeting Hint: Immutable commits are used to protect shared history. Hint: For more information, see: - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits - `jj help -k config`, "Set of immutable commits" Hint: This operation would rewrite 1 immutable commits.
So we are protected from accidentally deleting stuff on the main
branch.
Protected commits are called immutable and Jujutsu represents them in the log graph with a diamond (◆
) symbol.
Unprotected commits are called mutable and represented with a circle (○
).
These protections don't just apply to jj abandon
and jj rebase
, but to all commands that edit existing history.
We'll learn about a lot of commands like that in the next level.