Jujutsu is the Real Deal
An experience report from using jj as my primary git frontend for a few months.
I'm one of those older generation of software engineers who tends to view the new weekly tech fads with a bit of suspicion - not exactly quick to jump on the next bandwagon. But after seeing the new Jujutsu VCS pop up on the lobste.rs homepage a bunch of times over a few weeks, I had to at least investigate.
Jujutsu is for sure an improvement on git. It includes a simpler, more DAG-forward data model, commands that push you towards a better workflow, and dramatically better conflict resolution. But what really sets it apart from the numerous VCSes that can be said to be "better" than git, is the upgrade path to begin using it.
The data model
JJ does include an immutable, checkpoint-oriented "state of the whole tree" object like git commits (we'll talk more about how alike they are later), but those aren't the primary objects you work with. Wrapping a sequence of those commits, the main object is a jj "change". The sequence of commits don't point to one another as a linked sequence of commits, but rather look more like the result of repeatedly performing "git commit --amend", where each subsequent commit replaces the one that came before.
The star of the show is jj's change object. This is where JJ's DAG lives, and while you can create "bookmarks" which behave like git branches, you generally don't for local development but rather just keep an eye on the actual full DAG of changes and operate on that. It's much clearer to operate this way because the "jj log" command behaves like "git log --graph" by default, always surfacing the graph for you - and "jj log" is what you get by default by just running "jj".
I've been the resident "git expert" on teams a few times before, and from being called in to clean up git messes dozens of times, I can say with confidence that this DAG-centric view of the world will resolve 90% of the ways people mess themselves up in git. While git does have a graph of commits under the hood, and branches are "merely" movable pointers to commits in that graph, the available commands and mental model that they push leads people to primarily thinking in terms of linear branches. JJ doesn't make this mistake.
But probably the biggest killer feature of jj changes is that they are used far more broadly than git commits. JJ has no "index", rather it always keeps your "current commit" up to date with whatever is in the working tree. This also removes the idea of "unstaged changes". It's as though your every invocation of "git" was prefixed with "git commit -a --amend; ". You never have to stash anything, because "jj edit" which moves your "current commit/change" to another change will always immortalize your current working tree in the change you are leaving. And it lets the command set focus *just* on jj changes, rather than the many other states of changes that exist in git.
The workflow(s)
Another broken part of git's model that jj's changes fix is the dual nature of git's commits. On the one hand they represent any savepoints/checkpoints that you want to make before working on another feature on another branch, or look at someone else's diff in your working tree, or whatever. But those frequent savepoints get really cluttered - git users are familiar with the frequent commit descriptions of "fixes" and similar. So often git users end up wrapping up a feature by spending some time in git commands to rebase/clean up their history - merging the long string of frequent savepoints into a smaller number of clear "complete a feature" commits with nice properties like taking the build from green to green. Others skip that step entirely and perhaps give up on the idea of ever being able to meaningfully revert a feature with git commands.
So which is it? Is a git commit this transient "quick save" of the working tree state, or a final artifact that provides an actual useful history? It's both, and it takes some extra work on the part of the user to accomplish both tasks well.
JJ's changes, as mentioned before, include a sequence of commits. JJ really eagerly amends to the latest commit of the current change - these are your frequent save points. The wrapping change is what holds the "description" (allegory for git commit messages), and will be useful as the final historical artifact. You can even write the change description up front before starting the work, and then get going on the sub-iterations towards that stated goal.
I've noticed that lots of more sophisticated git users tend towards frequently amending to their current commit, as a way of splitting the difference between these meanings of a git commit. You can view JJ as a really simple automation of this workflow (except that it gives you tools to go back to an earlier revision of any change).
In the absence of an "index" a la git's, jj users tend to create a change on top of the one they're building up as a final historical change, and have that as the frequently-amended "current change". Then a periodic "jj squash" will *move* changes from the current commit into its parent - this is the replacement for staging in git's index and then flushing into a commit, but because in jj it's all changes, you end up with these commands being useful for other repository surgery between existing changes.
These commands being so good at operating on changes also feeds the strength of the earlier point about not needing branches. You see the DAG of changes with "jj log", and can operate on them (rebase individual changes or whole subtrees, move diffs around between changes, etc) with a powerful toolkit.
Conflict management is utterly revolutionized
In jj, conflicts are actually included in the data model. This means you can perform a rebase, find that it results in a conflict, and put off dealing with it until later - in the meantime you want to jump over to a different change and work on something else. You can continue from where you left off later and fix the conflict directly, or perhaps even resolve it by performing another rebase. That sinking feeling you get using git when a conflict shows up, and knowing that you're going to have to deal with this before you can *do anything else* with git, is completely gone.
The real magic trick: the path to getting started using it
All this is great, but plenty of later VCSes can claim to be better than git in these and other ways, but we aren't using those either because we have to work on teams, and git is completely dominant. Jujutsu lets you have your cake and eat it too. It's the first VCS that I'm aware of with pluggable storage back-ends, the first (publicly available) of which is, in fact, git itself. Currently, every jujutsu repository wraps a git repository, which you can use to work together with others who don't even have to know that you're using jj and not git directly.
So when I said "You can view JJ as a really simple automation of this [git] workflow", I *really* meant that. This is also why the revision objects within jj's changes are so much like git's commits - they actually are git commits. I've been using jj now for about 6 weeks as my *only* VCS tool, and have continued uninterrupted working with a team that uses git and github. There's really no reason not to give it a go.
~tjp
---