I’m writing this post after having used Jujutsu1 for a few weeks. This is some kind of “experience report”, as well as yet another2 love letter to Magit.
My background
I think I’d classify myself as a git power user. I’m sure I don’t know everything about the intricacies about the Git command line interface, but I do know my way around it. I like to believe I am one of those guys that colleagues and friends call for help when their local repo inevitably gets borked.
Git is the only VCS I’ve used3 since my beginnings as a young warlock apprentice computer science student, 10 years ago. So although in hindsight I’ve come to understand why people criticize its CLI so much, it’s always felt familiar to me, and it’s the only way I’ve known how to do things for a long time.
In addition to being quite blind used to the quirks of its CLI, my main way of interacting with git has been through Magit, ever since a bearded wizard my now PhD advisor showed it to me about 8 years ago. At the time, I was a convinced Vim user. After trying Magit, I decided it was compelling enough to switch to Emacs4 and never looked back. Yes, Magit is that good.
Discovering JJ
I’ve been reading about jj through blog posts, tutorials, and its official documentation. Everything that I read made me want to learn more and try it. In no particular order:
- A lot of workflows look simpler than equivalent ones in git, so it feels like there a more ways to “officially” use it depending on your preferred mental model
- I really like the notion of changeid being invariant to history rewrites5. This leads to forge workflows that are more similar to what Gerrit or Phabricator enable6.
- First-class conflicts are awesome! Pijul had them as well.
- The git backend is a must, this is what allows using jj transparently in git repos, and switching to jj full time without bothering your collaborators with your new shiny technology that they don’t really want to learn about. This “backwards compatibility” is a key feature, and I believe it is what makes the difference between a cool experiment, like Pijul, and a real path forward.7
- Mega-merges look really handy when working on multiple unrelated features at the same time.
So of course I bit the bullet. A few lines of Nix code later, I was running my first jj commands and following along Steve Klabnik’s excellent tutorial. After finishing that, I decided that I’d try using it in my “real-world” repositories, and forbid myself from using the git binary.
And… that’s where things went wrong.
Magit ruined the CLI experience for me
Yeah. In practice, I feel quite frustrated when using jj, not because it feels worse than git (on the contrary, I can confirm what everyone says! It does feel way better than doing everything with the git CLI, it’s a net improvement), but because it feels worse than Magit.
Now, of course a simple CLI cannot really rival with a GUI application. I’m not saying jj on its own should feel like Magit for me to even consider it. I’m saying, I’ll need a Magit equivalent before the jj experience feels better than what I currently have. And that’s okay! It will happen eventually. In fact, there is such a project being developed already, which I tried to load into my configuration, but got an error which I couldn’t find a fix for. The creator of the plugin mentions that it is in “pre-alpha” stage, and that as such, he is not accepting issues or pull requests. As he states, the plugin is
inspired by magit and humbly not attempting to match it in scope
So I’ll keep an eye on it, though it doesn’t seem to have all the features I need yet. Who knows, maybe once I’m done with my thesis8, and the author opens up PRs, I’ll contribute to it?
Of course there are other GUI/TUI options, some of which look quite usable! But they’re far from matching the experience I get within Magit.
One of the things I like to do often when writing code is iterating on the patch “as I go”, incrementally:
- I write some code, maybe some of it should be part of one commit, maybe some of it should be part of another.
- When I’ve written enough code for it to execute meaningfully, I build it, run the executable or some tests to check on the new expected behaviour, fixing it if necessary.
- Then I stage some of the changes I just made that I think should be part of a single commit. But I’m probably not done yet! The feature surely needs more polish, so I go back to step 1, write some more code, test it, stage it, rinse and repeat.
With the git CLI, that process would be painful. Each staging operation would need a call to git add -up and its clunky UI9, and each “review” operation would need calls to git diff or git diff --staged, depending on if I want to look at what’s staged already or not. Magit makes it a breeze though! It displays both staged and unstaged changes in its main status buffer, and allows to visually select individual lines of hunks for staging. At the press of a single s key I can stage parts of the code I want.
Within git, this means I’m making heavy use of the distinction between the working directory and the staging area: some code lives in the working directory only for a while, maybe because it belongs to another side commit that I’m planning to complete later. And some code lives in the staging area, where I’m incrementally building up my patch, reading through it between each iteration to check how the patch is looking.
Now, one of the fundamental differences in jj is that there is no staging area. Your working directory is automatically “staged”/snapshotted with every jj command you run, and there’s even an option to snapshot @ (your working copy) on every file modification!
Thankfully, there is a way to get an equivalent workflow. If anything, Jujutsu is more flexible in terms of which workflows it allows. The one I’m using is called the “squash” workflow, because it makes heavy use of the jj squash command. Steve’s tutorial has a whole section about it, so I’m not going to explain how it works in detail, but in a few words, you can use jj squash -i to do the equivalent of git add -p and selectively move some of the changes to the parent10 of the working copy.
So you have:
- Your working copy @ which would be the equivalent of git’s working directory in this workflow11
- The change you’re actually building, @-, which is the parent of @.
- git diff and git diff --staged are replaced by jj diff and jj diff -r @-12 respectively.
In practice it looks like this:
So jj supports what I want, all I’m missing is a great Emacs package that enables this workflow!
Where jj still shines
Magit makes a ton of stuff almost as painless as they are in jj. For instance, whenever I need to rewrite some commit history, I’ll always go for Magit if that’s an option. Complex history rewriting operations are available with a few key strokes, and Magit takes care of building the arcane command line invocations for you. No more typing git commit --fixup rev and git rebase -i --autosquash by hand, and there’s even some very handy shortcuts like the magit-commit-instant-fixup command, bound to F in the commit menu, that basically run both previous commands one after the other, avoiding the need to manually rebase to squash the commit into its relevant change. So all these years I’ve almost been getting jj’s automatic rebasing for free!
But even Magit can’t fix everyting about git: after all, it’s still built upon it. And if there’s one thing where Magit is helpless compared to jj, it’s regarding conflicts. jj has first class conflicts, and doesn’t require you to fix them when they happen: you can delay the resolution indefinitely. That’s not the case in git, and sometimes my super duper cool Magit flow gets interrupted by a pesky conflict that I have to fix immediately.
I’m sure there’s some other stuff where jj is clearly superior, but I certainly haven’t used it enough to know about all of it.
Conclusion
I guess this post is both a way to point the curious reader at a new very cool VCS, as well as a way to show my appreciation for an old tool that I realize I’ve taken for granted for the past 8 years.
Forgetting about my user interface expectations for a second, I think jj is a massive UI improvement over the git CLI. Anyone that is not using a superior GUI can probably benefit from switching to jj right now, and I encourage you to try it!
I’ll keep using jj in small projects where being able to incrementally build up my patches is not as important (for instance, I wrote up this blog post and resurrected my blog setup using jj for all changes!). That way I can follow the development closely, and who knows, maybe I’ll end up itching for that Magit equivalent so much that I’ll start my own little Emacs experiement.
Know more about Jujutsu
In case you landed here without knowing what jj is, here’s a non-exhaustive list of cool resources about it:
- https://steveklabnik.github.io/jujutsu-tutorial/
- https://v5.chriskrycho.com/essays/jj-init/
- https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj-absorb/
- https://kubamartin.com/posts/introduction-to-the-jujutsu-vcs/
- https://ofcr.se/jujutsu-merge-workflow
- https://neugierig.org/software/blog/2024/12/jujutsu.html
- https://justinpombrio.net/2025/02/11/jj-cheat-sheet.html