Jujutsu: a new, Git-compatible version control system
Jujutsu is a Git-compatible distributed version control system originally started as a hobby project by Martin von Zweigbergk in 2019. It is intended to be a simpler, more performant Git replacement. Jujutsu boasts a radically simplified user interface and integrates ideas from patch-based version control systems for a novel take on resolving merge conflicts. It is written in Rust and available under an Apache 2.0 license.
Unlike some other projects that build on top of Git — such as
Gitless or
Magit — Jujutsu is designed with eventual
independence from Git in mind. Jujutsu's own code is written in Rust, but it links
libgit2 (a C implementation of core Git features)
to interact with Git repositories. Jujutsu can either use a Git repository as its
storage backend, or use its own custom storage backend. The native
backend is not yet ready for serious use.
The project's README
states: "The backend exists mainly to make sure that it's possible to
eventually add functionality that cannot easily be added to the Git
backend
".
Von Zweigbergk, who is now paid by Google to work on the project,
plans to extend Jujutsu with a backend for Google's internal cloud-based
storage (as shown in
the slides
of his
2022 Git Merge talk). Jujutsu is designed to fetch information from its storage backend
lazily, specifically to support large
monorepos
like the one used at Google.
For now, Jujutsu seeks to be usable as a Git replacement for simple, everyday workflows. The core contributors use it to work on the Jujutsu repository without significant problems. However, the project still lacks support for some of Git's more esoteric features, including submodules, partial clones, shallow clones, multiple work trees, sparse checkouts, signed commits, and Git Large File Storage (LFS).
Also difficult to do without is the lack of support for hooks — external scripts that can be called at different points during Git's operations. Git used to have many components written as shell scripts, making it easy to add hooks to be called for various operations. Even though many core components have been rewritten in C, Git has maintained backward compatibility around when hooks will be invoked. Git contributor Elijah Newren cited this as one of the difficulties with improving the performance of Git's rebase operations.
There are plans to add support for a subset of hooks to Jujutsu, but this is more difficult than it might appear. Unlike Git, Jujutsu aims to have all operations work directly against the underlying storage, with any necessary changes rendered to the filesystem afterward. This choice allows Jujutsu to work with the same commands in a bare repository, and to perform more efficient in-memory transformations without touching the filesystem, but makes it challenging to support hooks. Since hooks are external programs, they usually expect to interact with a repository through the filesystem, which is impossible when operations are performed directly in memory. This lack also means that Jujutsu has no configurable merge strategies.
Jujutsu's from-scratch design with performance in mind means that it can complete rebase operations significantly faster than Git can. Jujutsu's superior performance has driven Elijah Newren and Christian Couder to propose a new "git replay" command that makes rebase operations faster. It does this partly by adopting reasonable defaults (such as making the equivalent of "--reapply-cherry-picks" the default), and partly by avoiding walking the commit graph as much as "git rebase" does. Walking the commit graph to obtain extra information that would reduce the number of required merges was a reasonable performance optimization when "git rebase" was first introduced, but now other performance improvements to merging have made the tradeoff not worth it for most situations. While "git replay" offers a significant improvement over "git rebase", it still lags behind Jujutsu's rebase operation.
Features
Jujutsu differs from Git in several other ways. The most striking is perhaps the removal of the index. In Jujutsu, the working tree is represented directly by a real commit. Editing that commit can be done directly by editing the files on disk, with no need to stage or unstage them. Running a Jujutsu command will copy any changes from the filesystem into the working commit before taking other actions. To finalize the commit and stop editing it, the user simply creates a new working commit on top of the old one with "jj new". Since the working copy is a commit like any other, Jujutsu doesn't need any equivalent of commands that modify the index, such as "git add" or "git stash".
Most commands in Jujutsu affect the working copy by default, but can perform the same operations on other commits by specifying their revision. For example, "jj describe" is used to alter the commit message of a commit. If invoked without any other arguments, it alters the commit message of the working copy. If invoked with another revision, it alters the commit message of a historical commit and transparently rebases everything that depends on it. Other commands such as "jj move", that moves a diff between commits, work on the same principle.
Automatically rebasing commits like this, while convenient, creates its own set of problems, especially when rebasing commits that have already been sent to other developers or a centralized server. To ameliorate the many conflicts that implicit rebases create, Jujutsu uses an idea from patch-based version control systems such as Darcs and Pijul. These systems allow one to commit conflicts and resolve them later. This does not involve committing the textual conflict markers, but rather a representation of the conflicting trees. Then those conflicts can be resolved in later commits.
Von Zweigbergk gave an example of how this works. Consider a Git history that looks like this:
o---*---o topic
/
o---o---o---*---o---o main
The two commits marked with asterisks have conflicting changes. The author of this code would like to merge the main branch into the topic branch, resolve the merge conflicts, and then later rebase the topic branch onto the main branch to eliminate unnecessary merges. With Git, the author could use "git rerere" to remember the conflict resolution and replay it later while rebasing. With Jujutsu, they could simply rebase on top of the main branch and fix the conflict, resulting in this history:
o---*---o---+ topic
/
o---o---o---*---o---o---o---o main
Now if the author inspects this history with "jj log", they will see that the commit marked with an asterisk on the topic branch is marked as having a conflict, as is the commit after it. The commit with a plus is the one that resolves the conflict. Later, if they want to clean up their history, they could move the conflict resolution into the original problematic commit and obtain a clean linear history like so:
jj move --from <plus commit> --to <star commit>
By storing conflicts in this way, Jujutsu can ensure that merges and rebases always "succeed" — they might just leave some conflicted trees that can be dealt with afterward. Conflicted trees can be committed, checked out, and edited like ordinary trees. Conflicted files are rendered with textual conflict markers like the ones that Git adds after a failed merge, but are represented internally as a series of diffs. One use case for which the approach of storing conflicts might not work as well is bisecting, since conflicted commits may not build correctly. There are plans to add bisection support, but the implementation is not yet finalized. One advantage of representing conflicts in this way is that Jujutsu doesn't need an equivalent of "git rebase --continue". Every rebase and every merge completes in one command, without stopping in the middle to ask for assistance, as Git is prone to do.
This permits Jujutsu's final headline feature: the operation log. Like Git's reflog, the operation log tracks previous states of the repository. Unlike the reflog, the operation log treats rebases or other complex operations as single atomic entries. The operation log then powers the "jj undo" command that can undo the effects of any other Jujutsu command.
This combination of fast, atomic, pervasive rebases provides a different vision of how to manage a repository. Whether Jujutsu's user interface is ultimately an improvement remains to be seen, although the radical simplicity of its design is promising.
Trying Jujutsu
While Jujutsu has not yet been packaged for most distributions (the exception being NixOS ), readers interested in trying it can download a precompiled version or compile the source. Cloning an existing Git repository for use with Jujutsu is done with "jj git clone". Cloning Git repositories with many refs can be slow, and the Jujutsu documentation warns that hybrid repositories that use Git and Jujutsu together may see Jujutsu commands run slowly because of the need to check Git refs for changes between commands.
The command-line interface is fairly similar to Git, except for the omission of commands to manipulate the index. One difference to be aware of is that "jj log" only shows local commits by default. To show the full history of the repository, one uses:
jj log -r 'all()'
The Jujutsu documentation has a thorough introduction and a comparison between Git commands and Jujutsu commands.
Conclusion
Jujutsu has come a long way in only a few years. It is already usable for working on projects that don't need more complex Git features, and offers a more consistent user interface, better performance for some operations, and an interesting approach to conflict resolution. With ongoing support from Google, it seems likely that Jujutsu will continue to see active development.
At the same time, Jujutsu lacks some of the features that make Git flexibly adaptable to different use cases, such as hooks or submodules. Importing many refs from Git remains slow, and there are still some rough edges around getting a repository initially set up. Whether Jujutsu will come to be used outside of Google depends on if its simplified interface wins out over its reduced applicability to uncommon workflows.