jj is one of the few new devtools I was quite frustrated with while starting out but could see the value so stuck around and man is it so nice.
For anyone curious the big thing with jj is you can undo. Basically if you were running a rebase and bungled a commit sha or branch name, no worries undo your way out, if you forgot to push but abandoned a commit not issues undo it. Same with all jj commands.
Delta db takes this a step further and everything you edit is a change so your actual edits are also all saved.
I find non destructive-ness for as long as possible approach very nice, since there aren't as many constraints on us not being able to do it nowadays, we had limited storage and stuff in 2000s perhaps but in 2026 even with the killer SSD prices it's fairly cheap to buy more SSD, we can definitely waste 100MB per project storing redundant history we may not ever need.
Same way in photoshop non destructive editing took over, especially useful for humans, but works for AI and LLMs too, they can undo their mistakes as well.
How is this undo different than Git's reflog? Genuinely curious, while I have heard of jj I haven't yet tried it (I read it couldn't handle git submodules, which would have made it dead in the water for my dayjob, that seems to still be the case).
1. Reflog is very ref specific that is it tracks head. JJ has op and evo logs that do that + a bunch more they track everything about the repo every possible action. It's hard to explain with going into technical depth but you can read up on it.
2. Yeah submodules aren't supported but since you can use jj in a git repo you can commit git stuff via git like submodules and then enjoy the world of jj. Since submodule interactions should be rare imho. I use it for a bunch of projects with submodules.
reflog can't quite catch every change that might happen because not everything is stored directly as a ref (ie a "head" that git tracks, like a branch pointer).
For instance, if you are interactively using `git bisect` and you mark commits as good or bad, and you accidentally mark a commit incorrectly, you have to do something like:
The reflog can't really capture this kind of thing, hence why you have a bisect log -- now a wholly separate concept that exists independently of the reflog.[1]Another example is when you do something like screw up an interactive rebase. Let's say you rebase 20 commits and then you get a conflict on commit 8. You fix the file conflicts, and continue. You accidentally solve the merge incorrectly, continue and get another conflict -- but only realize your mistake after you start solving it. The reflog can't save you here. You have to completely abandon the rebase and start over. (This specific example might be handled better these days).
I think the biggest thing about `jj undo` is that it works everywhere. You can undo rebases, merges, conflict resolutions, copies, deletions, whatever. The secret behind it all is that internally, jj is architected in a way where implementing a feature looks like you are working with a transactional database. You actually have `begin_transaction()` and `commit()` methods in the codebase that will make changes to the commit graph visible in an atomic way. When a command like `jj rebase` happens, all of the changes it makes are inside a transaction and committed at once. Every operation in the repo is a transaction, and it all goes into a log, which records the effects of a transaction -- very much like a database system!
So "undo" just means "undo the effects recorded in a transaction" and that is about all. And so it works for everything! And this design is very easy to intuitively understand and program against, as a maintainer, along with our other high level internal APIs. Any developer can easily write code that Just Does The Right Thing and the user can undo it and it's no big deal. When I develop and work on Jujutsu myself -- like I'm actively developing new features or prototyping ideas -- I almost always _use my own jj repository_ as a test repo while testing my builds.
In contrast, Git does not have one unified "transactional" layer for things like this. But not all is lost, there has been work on 'git undo' and it was implemented by... Someone who is now a Jujutsu maintainer[2]. :)
[1] Technically we do not yet have "step by step" bisect with good/bad yet (only "automatic" bisect that is one-shot), so that is something Git can do we can't do at all right now, but bear with me. :')
[2] https://blog.waleedkhan.name/git-undo/
A term coined within Google that describes the best part (to me at least) of jj: delayed conflict resolution.
You worked on multiple things, you sync with the upstream, then boom now you have conflicts in one or more of your local branches.
Instead of immediately presenting you a conflict resolution view (usually 3 pane vimdiff) and you'll have to resolve at the sync time, the commits are simply marked as "have conflict", and you can continue working on other things until you have time to deal with it.
And for a chain of commits that all have conflicts, chances are fixing the first conflict automatically resolves all other conflicts in the chain, thanks to the auto-rebasing feature.
> And for a chain of commits that all have conflicts, chances are fixing the first conflict automatically resolves all other conflicts in the chain, thanks to the auto-rebasing feature.
What happens if one is unlucky and the conflicts are not automatically resolved from changes to the first commit? Does that potentially imply more work overall than if one was using a VCS that forced conflict resolution earlier, like git?
No worries, it's still just another commit w/ conflict. If you want, solve those conflicts later.
Why is git's "forced conflict resolution" better, assuming the same workflow (local change+sync upstream) would produce the same # of conflicted commits?
It’s fundamentally no different than doing the same resolution in git.
The main advantages are that you can do the changes piecemeal, in any order, at any time, and by jumping around in the commit history however you need or want to.
Sometimes the best way to solve a conflict is by changing something before the conflict, or by moving pieces from one commit to another. Git doesn’t allow this, you can only do: fully fix commit 1, fully fix commit 2, fully fix commit 3, and so on.
git doesn't force conflict resolution earlier. It forces conflict resolution the moment you pull changes from upstream. jj allows you to go back to the first conflicting commit and then fix things there. That first conflicting commit is most likely a lot more similar to the upstream code than your newest commit and therefore it is easier to fix.
Are you saying push + undo = unpush?
jj itself doesn't have a native ‘push’. `jj git push` and `jj piper upload`¹ operate on external systems that don't necessarily support undoing arbitrary operations. Pure speculation, but if I were ESRC I'd make my backend handle it (subject to enterprise audit policies).
¹ https://drive.google.com/file/d/1dVzug1lHoOxdbFu8gcCJCu-G_uV...
Reminds me of the git-svn bridge.
Points of no return.
You can undo a push, yes. It undoes the local state of the remote branches after the push operation. ie: branch@origin will point at the commit it did before the push action. It doesn't actually update the branch on the remote. That would require another push operation.
AFAIKT it will revert the local state to "pending push".