JJ: Why Git Is Outdated for AI-Assisted Development

Imagine this: your Claude Code or Cursor AI agent deletes a file or starts rewriting it from scratch. The file contents haven't been saved in a git commit yet—it's still in an unstaged state. Everything is gone. All you can do is hope the AI agent can reconstruct something from memory and checkpoints, but these are workarounds at best—and you might miss the moment.

When I switched from pure Git to JJ on top of Git, this scenario became impossible. I became much more confident about my code while working with AI agents. Let me show you why JJ will improve your life as a programmer and how to integrate it into your workflow without breaking your team's existing processes.

Why Git Doesn't Handle Parallel Development

Git is fundamentally outdated for systems where code is being written in parallel with you on your machine.

Git's paradigm is simple: you collect some changes locally, put them in a box, label that box with a hash identifier, and send it through a pipeline of boxes. That box seems immutable to you—you'd rather not touch it again, except maybe to read what was done in it. If something happened before you assembled the box, you just write it again. Usually you haven't gone far, and what could go wrong anyway?

In gaming terms, git commits are like manual saves you make by hand. But you don't have autosave.

The Underlying Tension

For years, this concept worked brilliantly. Though we must admit, Git's interface for all its possibilities has always been quite complex. The ongoing struggle with how commit history looks across different companies suggests something might not be optimal in this system.

JJ (Jujutsu) is designed to solve Git's usability problems while maintaining full compatibility with existing repositories and workflows.

JJ is a version control system that sits on top of Git, providing a much more intuitive interface and powerful features for managing your code history—especially when working with AI agents.

Let me show you three core concepts that make JJ powerful:

  • JJ log operations: undo and redo
  • Working copy as a commit
  • Advanced operations: squash, split, edit

Part 1: JJ Operations with Undo/Redo

Let's set up your first JJ project:

mkdir jj-step-0 && cd jj-step-0 && jj git init
touch README.md
echo "first try" >> README.md
jj diff
Screenshot (jj diff)

If you run the command jj log, you'll see that you already have a Change ID and Commit ID. On the left is the Change ID, on the right is the Commit ID.

Change ID is a permanent identifier of the logical task you're working on. Unlike a Git hash (which in jj is called Commit ID), Change ID doesn't change when you edit files, change descriptions, etc. Change ID never changes—even if you do amend, rebase, or edit a commit message. The main commands jj edit, jj describe are performed on the selected Change ID.

Change ID is a random 16-byte number, represented as a string of 12 characters from z-k. This makes it look unique and easy to read.

In jj, the working directory is always a "live" commit. Any change to files immediately becomes part of the current revision. The working copy is marked with the @ symbol in the output of jj log. The commit hash will change with each edit you make in the project. That's why it's not so important during work.

Tip

  • Add a description to this commit (via jj describe).
  • Create the next empty commit on top (jj new) to "close" the current one.
  • Check the status of files to make sure everything is tracked (jj status).
jj commit -m "First"
jj
Screenshot (jj first commit)

Our first commit is created. And at this point there are no changes in the new working directory, so it's time to start writing a feature with the help of an AI agent.

echo "Cool staff" >> README.md
touch new-file.md && echo "One more thing" >> new-file.md
jj diff
Screenshot (jj diff with changes)

Something terrible happens: your AI agent decides to rewrite new-file.md from scratch and deletes the original content, replacing it with something strange.

Let's simulate what the AI did:

rm new-file.md
touch new-file.md
echo "Bad staff" >> new-file.md
Screenshot (bad changes by AI)

What now? We're not going to beg the AI to restore our work. Instead, let's see what JJ has saved:

jj evolog --color=always -p
Screenshot (jj evolog)

Good! JJ maintains a history of all changes in the working commit (there's no unstaged here), as well as a history of all operations you've performed. Each modification, deletion, or addition is a separate operation with its own hash. Now let's undo what the AI did:

jj undo
jj diff
Screenshot (jj undo)

Done. And our "Cool staff" is back in place. If we realize the AI was right, we can go back with:

jj redo

Important: undo and redo operate on operations, not file changes. If you made a commit and realized you need to add something else, just use jj undo.

Key JJ Commands for Operations

TaskCommand
See how a change evolvedjj evolog -p
Undo last operationjj undo
Redo after undojj redo
View operation historyjj op log
Restore specific statejj op restore <operation-id>
Compare two versionsjj op diff --from <a> --to <b> --git

Part 2: Managing Commits and History

Now let's explore how JJ simplifies managing commit history and messages. Let's start a new project:

cd .. && mkdir jj-step-1 && cd jj-step-1 && jj git init

Let's work on three features in the project:

touch file.md
echo "Feat 1" >> file.md
echo "Feature 2" >> file.md
jj commit -m "Feat 1 & 2"

touch file2.md
echo "Feature 3" >> file2.md
jj commit -m "Feat 3"
Screenshot (feat 3)

Wait, you realize you should be writing "Feature" everywhere, not "Feat" in your code. The new company rules require it—did you miss that? We need to go back and fix the first commit. With JJ this will be incredibly simple.

jj edit r
Screenshot (jj edit r)

Notice we're using just the first letter r instead of the full Change ID value. JJ uses "minimally sufficient" commit references—just enough characters to uniquely identify a revision without collisions in your current project context. As your project grows, more characters will be needed.

Now we're "inside" the first commit. We can modify file.md as needed and change the commit message with:

jj describe
Screenshot (jj describe)

Splitting Commits

Since we're here, let's split the first commit into two separate commits—one for each feature. Well, you shouldn't do different tasks in one commit. This is incredibly simple with:

jj split

Press 'F' to unfold all changes, select "Feature 1" and confirm with 'c'. Set your commit message to "Feature 1", save, and set another message for the second commit. You should now have a clean history.

Screenshot (jj split)

Screenshot (jj split success)

Return to our latest commit with the third feature:

jj edit u

And start working on a fourth feature:

jj new -m "WIP 4 feat"
touch README.md && echo "Blazing fast project" >> README.md

JJ has a concept where you don't need to use the commit command explicitly. You can start work by preserving the old commit with a description, and begin work on something new with a description like "WIP 4 feat". Later, at any moment, you can rename the commit description and start a new one. This concept explicitly tells you what work you've started and what you're currently doing.

Wait—README.md should probably be part of the first commit. That's not right! But there's no problem, just squash your current work into the first commit and continue:

jj squash --to r
Screenshot (jj squash)

Edit the commit message and you're done!


Conclusion

We've practiced with JJ and studied its powerful and user-friendly interface for Git. The beauty is that you can use it without changing your team's processes—JJ works on top of regular Git.

You can maintain cleaner history and worry much less about AI accidentally damaging your local work. The confidence this brings is invaluable in our anxious world.

Of course, I can't promise a seamless transition. Mastering tools like JJ takes a long time, and you might hit moments where pure Git seems easier. Honestly, I accepted JJ only on my second attempt at using it. Like trying olives for the first time, it might not appeal to you immediately—but if you keep going, everything will work out just fine.

I haven't covered everything JJ can do, but I hope this was helpful and that you'll feel inspired to try it and make your work even better.


Subscribe

Subscribe to my channel at https://zatsepin.dev/subscribe for access to my exclusive content, project source code, and much more.

References