Jujutsu (jj) VCS workflows and the convenience of its "operation log"
Over the last month or two I’ve been trying out the relatively new jujutsu version control system (VCS) — and loving it. At first, I discovered it and read their GitHub README with curiosity but skepticism. Then I sought a few YouTube videos1 demonstrating jj in action, and a tutorial for a text-based reference. I became very interested. I initialized a jj repo in one of my small git projects and played around. Since then, I’ve migrated all of my personal repositories to jj, and use jj for practically everything.
From the time I’ve spent with jj, I’ve felt myself sliding into some general workflows:
- A jj squash workflow
In git, this would be a workflow where you make a commit and continually amend/extend that commit until its intended goal is completed.- Make progress toward a desired goal.
- Commit (jj commit, or jj new then jj describe -m “…") with a message describing this unit of work.
- Continue making changes.
- Squash (perhaps interactively: jj squash -i2) new changes until the desired goal is accomplished.
- Maybe change the description of the previous commit commit.
- Work in the working copy
- Envision a goal for a new commit. Express that into the description of the working copy commit: jj describe -m “…".
- Work until much of that goal is met.
- jj new to work on the next goal. Or jj split -i if some of the changes in the working copy belong in the next commit.
- jj edit to hop between commits and work on each until completion. Perhaps jj new -A … one or more times when when you don’t want certain changes in a commit: make changes across however many of those commits, then jj squash them all into your original commit when you’re happy. (You’re basically creating a new git branch!)
- Concurrent commits workflow
Work on several commits simultaneously, using combinations of jj squash, jj new, and jj rebase to make make reversible changes while having a clean jj log at the end. In essence, this is point (3.4) above but with room for added complexity.
I’m not a VCS expert, and my projects are small, but I’ve gravitated to (1) and (2) without feeling any mental strain of keeping track of where this and that change is and what CLI operations avoid x problem or y merging conflict (which, by the way, jj permits! Jujutsu treats conflicts as first-class, so conflicts and their resolutions percolate through ancestors and descendants without problem.)3
Perhaps the most exciting part of using jj is fearlessly making changes and experimenting. This has been an extraordinary boon for my learning about how to use jj in accordance to its VCS concepts. The reason is jj’s operation log (jj op log) and the ability to revert back to any past repository state (jj op restore OPERATION_ID).
With jj op restore, I can merge branches, create conflicts, resolve conflicts, mess up… then go back as if it never happened. I can make sweeping repository changes without fear! In git, I’d be walking on egg shells, worried about the potential to ruin the entire state of my local repository if I’m not careful…
I’d definitely recommend trying jj out if you haven’t already! Easily turn a git repo into a jj repo with a git backend: jj git init --colocate.
I found this talk by the originator of jujutsu, Martin von Zweigbergk, a senior software engineer at Google, especially insightful with respect to the technical merits of jj over git. If jj is meant to potentially eventually handle Google’s gigantic monorepo (86 terabytes of data, ~2 billion lines of code across 9 million source files!), then surely there’s something to it! The talk justifies the existence of jj. ↩︎
Without the -u flag, jj squash -i effectively behaves like git commit --amend when your working copy has a description (e.g., with jj desc -m “…" or if you jj edit to a past commit). Without a commit, jj squash -i is like git commit --amend --no-edit. ↩︎
A great resource on the matter of “conflicts as first-class” is this talk by the mind behind jj. ↩︎