Learning Jujutsu
I am not learning to fight. I am learning the Jujutsu version control
system! I've used
git almost my entire programming career, and
I've learned to use it well[1]. Over the years I've acquired the
ability to do rebase workflows, stacked branches, fixups, reordering,
staging changes using git add -p, and many other things. I've got a
whole mess of git command aliases I'm hoarding like some kind of
Tolkien dragon. I've learned the magic spells but still aren't a
master of them. Even armed with this secret arcane knowledge, git is
still a very confounding and difficult tool to use.
JJ - the abbreviated name of Jujutsu - immediately solves many of the problems I encounter when using git. JJ has a markedly different set of ideas compared to git. Unfrtunately that means my git knowledge doesn't fully transfer, but I think that is alright. JJ actually makes a lot of that knowledge no longer needed. It is a much simpler tool to use and understand and I think that is really exciting.
Matty Note: This isn't intended to be a technical inventory of either of these tools, just some off the cuff remarks about using them. There are probably better examples of pain points - or even better ways of doing the things mentioned that make them less of a pain! These are just the things I am familiar with.
Sharp Edges of Git
Many git users actually use a "front end" that makes interacting with git easier. Some folks use plugins provided by their Interactive Development Environment (IDE for short) to make their git workflows easier to handle and more visual. IDEs usually also integrate essential tooling for conflict resolution, and diff/history viewing. These tools are indespensible. I've been a user of the magit git porcelain inside of emacs for several years. While I use an IDE like VSCode for most of my work, I've installed emacs on all the work machines I've touched just to have access to it.
Git is an amazing tool, but everyone except the most powerful wizards seem to need a little help getting the most out of it. I think most of the issue with git is that there are several different commands you need to master to do common operations and some of them you need to do in concert with each other.
For example, when doing a
rebase
to replay commits on top of some branch, you may run into conflicts
that require resolutions immediately. These resolutions then need to
be staged with git add. After staging the conflict resolutions you
will need to continue rebasing your commits by saying git rebase --continue. You have to be careful not to actually commit the staged
changes during a rebase! This is wildly confusing. Normally when you
add you commit your changes. Not when doing a rebase!
Interwoven incantations are also required when doing what should be
simple things like fixing a previous commit. This is important to do
if you don't want your git log to look like a truck drove through
it. You can "fixup" a previous commit a few ways but one method is to
do a "fixup" commit. You can make changes, stage them with git add,
commit them as a fixup commit with git commit --fixup <revision>. Now you'll have a fixup commit in your git log. You'll
want to merge the fixup commit with the <revision> you mentioned in
the commit command. You can do this with the convenient git rebase -i <revision> --autosquash. Now we both have a headache.
Notable differences in JJ
JJ streamlines a lot of this kind of stuff. I won't outline all the differences here, but I'll try to pick out a few things I am impressed with.
One surprising and exciting change is that JJ doesn't have the notion
of a staging area! Every change you make is automatically committed
to something called "the working copy" aliased to @. The staging
area in git is the source of a lot of friction when developing. If
you're working on a feature branch but need to jump back to the main
branch to fix an issue you will need to either stash or commit your
partial changes before switching to main and getting to work. In JJ,
because everything in the working copy is already automatically
committed, you can say jj new <revision> to create a new commit with
<revision> as the parent.
Fixing prior commits with changes in your working copy is as easy as
running jj squash --into <revision>. Child commits of <revision>
will automatically recieve the changes that are being squashed. This
can cause conflicts, but the conflicts don't have to be immediately
resolved like they do it in git. If a conflict happens at <revision>
that commit and all its children are marked as conflict commits. They
can still be edited and worked on as normal until you're ready to act
on the conflict. To resolve a conflict, you'd just insert a commit
after the conflict commit using jj new <revision>, resolve the
conflict, then squash the two together with jj squash. The primitive
actions in JJ are often used together in this way to perform more
complex actions, In git, they might require multiple different
commands like add, commit, rebase that would require multiple
different flags
JJ also has a notion of an "immutable" commit. From JJ's perspective, any commit that is tracked in some remote branch is considered "immutable" by default. JJ will stop you from running any commands that would be destructive to immutable commits. With git, you can destroy as much history as you want in your local repo with rebasing. This can be really tough to deal with when you have to reckon with changes coming at you from upstream.
JJ is unique in that all the changes to your repo are also
tracked. This enables us to undo actions! If you mess something up you
can just jj undo and go back to your previous state. I cannot tell
you, precious reader, how many times I wished I had that ability in
git after running some git command that I thought I understood. Often
with git I'd actually create a backup branch from the current branch
so I could try out a few commands and know I could return to the
safety of the first branch. That's not good!
There are many other little quality of life features that JJ has in addition to the ones mentioned above. The default log command I find to be much more useful than git's. JJ also provides different conflict markers than git which I find to be more useful and clear. These little things all add up to make a better experience. Check out some user testamonials and consider trying it out yourself!
JJ Resources
I am curently all-in with experimenting using Jujutsu as a git replacement. So far, I am having a great time. I really think JJ has the juice.
Some resources I've been looking at to learn JJ are listed below:
Most git users would say that they have learned enough git commands to be "dangerous" - a lot of git operations are destructive and can rewrite the change history causing lots of problems. ↩︎
- Previous: Audio Bookin'
- Next: La Luna