Rethinking version control with Jujutsu
Published: 2026-09-09
I’m partial to questioning the solutions that I use in my daily workflows. And this often manifests as me going down rabbit holes and trying out something new just for the sheer joy of learning.
Jujutsu or jj is one of those things that has tickled my curiosity. It’s a version control system (VCS) that aims to give developers a more human-friendly interface to manage codebase changes, while maintaining compatibility with local and remote Git repos.
It handles this by taking the VCS focus away from immutable commits and branch management and preferring to flatten concepts into mutable “changes”. Which ideally will lead to less friction when developing in a nonlinear fashion.
Since I base most of my infrastructure on Git and GitOps principles, I couldn’t fathom why I’d need this or how I could make it work with my current workflows. But, as I said, I was curious to learn.
JJ workflow vs the Git workflow
Coming from Git, jj feels like you’re working a little backwards most of the time. My general Git workflow ends up more-or-less following this flow:
- Create a branch declaring what I want changes I want to make/set a goal.
- Make changes
- Stage and commit anything I want to keep along the way, while trying to give each one a semantic description.
In jj, I’ve landed on doing things this way:
# Assuming we are starting on an empty commit or "change"
jj describe -m [CONVENTIONAL_COMMIT_MESSAGE]
# Create a new staging area. `jj` has no concept of staged
# or unstaged files, but it helps to use another change
# as a working space
jj new
# As I work on changes made in this staging area
# I move them down to the commit when I feel they're ready
jj squash
# As commit's are made by repeating the above steps,
# I can reflect on what they achieve together as a whole
# and assign them a bookmark or branch
jj bookmark set -r @- [BOOKMARK_NAME]
In jj you can also jump around to earlier changes by calling jj edit -r [REVSET] (the revset being jj's concept for referencing changes) and it handles any necessary rebasing to make it happen. In, addition I found myself reflecting on and amending “what I’ve done” instead of thinking about “what I need to do” when working with jj, and that took some time to get used to.
What I’m still working out
I’m still actively learning and assessing jj, have yet to pin down my preferred way to work with remote repos. Git is very branch based, but jj tries to soften that boundary by conceptualizing branches simply as “changes (with different IDs) with the same parent”.
Bookmarks serve as a means to bridge the gap between very concrete git branches and jj. But, what I’m working with still feels like there’s too many steps involved with what I’m trying to make happen.
To set a bookmark and push to remote:
# This creates a new branch and then pushes it to the default remote
jj bookmark set -r @- "new-feat"
jj git push --bookmark "new-feat"`
However, when examining the underlying Git repo, the upstream is not yet set for the new branch. I still have to manually set the upstream and check out the branch before I can draft a PR:
git checkout "new-feat"
git branch -u "origin/new-feat"
# Finally, I can get to drafting a PR
fj pr create "feat: new feature"
Hopefully, I can figure out a way to skip all these extra steps and get straight to creating the PR in the future!
Reflections
After using jj for a bit I can see the appeal! The nonlinear aspect of editing seems like a great boon for trying out different things before making commits. I kind of like this way of working, it lets you create this almost mercurial (for lack of a better word and not at all associated with the other VCS software) way of working. Your previous changes remain mutable and can be added to or reedited freely. jj feels more like an additive and subtractive process rather than being just the former.
Links
To learn more about jj I suggest this tutorial that really helped me grasp the basics. You can also find the full documentation here.