Exploring Jujutsu: A Modern Spin on Git

I still remember the day I first switched from SVN (Subversion) to Git. It felt like the future had suddenly arrived on my doorstep, offering the power to commit, branch, and merge in ways that SVN couldn’t easily match. Over time, Git became my bread and butter. Now, in 2025 and beyond, I once again find myself on the verge of a version-control transformation—this time by experimenting with Jujutsu.

Why try Jujutsu?

There’s a certain comfort in sticking with Git. After all, it’s ubiquitous, battle-tested, and has an ocean of resources supporting it. But sometimes, small frustrations or inefficiencies start piling up, making you wonder if there's a better way. Jujutsu (abbreviated as jj) stands out because:

  1. It’s backward compatible with Git. You can continue working in the same repositories as colleagues who haven’t adopted jj. When you need to create a pull request, you can simply push a Git branch without impacting your teammates’ workflows.

  2. Stacked branches/PRs are simpler. If you have multiple features building on top of one another, jj presents them in a neatly stacked layout, making it clearer how changes progress.

  3. You can combine commits or branches freely. With Jujutsu, you don’t have to wait for a branch to get merged into main before building on it. You can reference multiple changesets in a new feature branch, which is incredibly liberating for rapid development.

  4. It has better conflict markers. Merge conflicts become easier to parse, thanks to a cleaner layout.

Of course, nothing’s perfect. Jujutsu has a few downsides:

  1. It doesn’t have Git hooks. If you rely heavily on hooks to enforce policies (e.g., commit-msg or pre-receive hooks), you’ll have to find alternatives or revert to Git for those tasks.

  2. It has its own philosophy. Even though it’s compatible with Git, jj still approaches version control differently, which can be an uphill battle if your entire team isn’t on board.

What’s the Philosophy of Jujutsu?

Jujutsu aims to simplify Git workflows. There are no traditional “branches” here, and there’s no separate “commit” step after making changes. Instead, you work with a sequence of changesets. When you need a new set of changes, you create it with jj new and begin editing files. Once you’re ready, you can add a description (using jj desc) and finalize the changeset. For convenience, jj commit combines these actions—though it’s not the same as git commit because it just appends a description and seals your changes into a new changeset.

For example, here’s a sample working history:

❯ jj
@  klqwozss sergey.golovin@protonmail.com 2025-02-02 09:53:05 bc1c8fa9
│  fix: clear terminal before going back to the editor
◆  zurmvopq sergey.golovin@protonmail.com 2025-02-01 20:46:22 main git_head() e2286213
│  chore: update colors of status bar
~

To create a new changeset, just type:

❯ jj new
Working copy now at: nmkklpov 77f465f2 (empty) (no description set)
Parent commit      : klqwozss bc1c8fa9 fix: clear terminal before going back to the editor

You can now add or modify files, and those changes are saved automatically. When you’re ready, you can describe the changeset with jj desc or do it all at once with jj commit.

Do not wait! Just add this to your working branch

One of the coolest things about Jujutsu is that you don't need to wait for a feature to be merged into a main branch to build on top of it. Consider this history:

❯ jj
@  ltyrpuno sergey.golovin@protonmail.com 2025-02-02 10:07:35 1be73fee
│  (no description set)
│ ○  lkqryomk sergey.golovin@protonmail.com 2025-02-02 10:06:54 3e036390
│ │  (no description set)
│ ○  pzvmvtpw sergey.golovin@protonmail.com 2025-02-02 10:04:46 ffe78f2a
├─╯  (no description set)
○  nmkklpov sergey.golovin@protonmail.com 2025-02-02 10:04:31 git_head() 2a5dfd41
│  (no description set)
│ ○  uyyszzsz sergey.golovin@protonmail.com 2025-02-02 10:06:16 d9ba2396
│ │  (no description set)
│ ○  knmruutr sergey.golovin@protonmail.com 2025-02-02 10:05:12 b7498368
├─╯  (no description set)
○  klqwozss sergey.golovin@protonmail.com 2025-02-02 09:53:05 bc1c8fa9
│  fix: clear terminal before going back to the editor

If you want to create a new changeset that includes ltyrpuno, lkqryomk, and uyyszzsz, you could do:

❯ jj new u lk lt
Working copy now at: ptsmnxmz 362396b6 (conflict) (empty) (no description set)
Parent commit      : uyyszzsz d9ba2396 (no description set)
Parent commit      : lkqryomk 3e036390 (no description set)
Parent commit      : ltyrpuno 1be73fee (no description set)
Added 2 files, modified 1 files, removed 0 files
There are unresolved conflicts at these paths:
c.txt    2-sided conflict
d.txt    2-sided conflict

Conflicts can arise, but once you resolve them in Jujutsu, it remembers those resolutions. Future merges with the same changes won’t pester you again with the same conflicts.

After resolving conflicts, you might see something like this:

@  ptlrysws sergey.golovin@protonmail.com 2025-02-02 10:15:14 1e9b3710
│  (no description set)
○      ptsmnxmz sergey.golovin@protonmail.com 2025-02-02 10:14:51 git_head() 0078f3ff
├─┬─╮  (no description set)
│ │ ○  ltyrpuno sergey.golovin@protonmail.com 2025-02-02 10:07:35 1be73fee
│ │ │  (no description set)
│ ○ │  lkqryomk sergey.golovin@protonmail.com 2025-02-02 10:06:54 3e036390
│ │ │  (no description set)
│ ○ │  pzvmvtpw sergey.golovin@protonmail.com 2025-02-02 10:04:46 ffe78f2a
│ ├─╯  (no description set)
│ ○  nmkklpov sergey.golovin@protonmail.com 2025-02-02 10:04:31 2a5dfd41
│ │  (no description set)
○ │  uyyszzsz sergey.golovin@protonmail.com 2025-02-02 10:06:16 d9ba2396
│ │  (no description set)
○ │  knmruutr sergey.golovin@protonmail.com 2025-02-02 10:05:12 b7498368
├─╯  (no description set)
○  klqwozss sergey.golovin@protonmail.com 2025-02-02 09:53:05 bc1c8fa9
│  fix: clear terminal before going back to the editor
◆  zurmvopq sergey.golovin@protonmail.com 2025-02-01 20:46:22 main e2286213
│  chore: update colors of status bar
~

You can also edit previous changesets. For instance, if you need to modify nmkklpov, type jj edit n, make changes, and then return to your last commit. Any updates in the earlier changeset flow naturally into subsequent changes. This approach works just as well for long chains of stacked changesets. By running jj git fetch, then jj git push, you can synchronize all updated branches with the remote.

Though Jujutsu technically doesn’t use “branches,” it does support bookmarks, which are analogous to Git branches. You can manage these bookmarks via commands like jj branch create new-branch or jj b c new-branch, and then push them to your remote as you would with any regular Git branch.

Conclusion

Jujutsu offers a refreshing take on version control, blending Git compatibility with a simpler, more flexible workflow. Its stacked changesets make it easier to experiment, resolve conflicts once, and move forward without getting bogged down. While there are trade-offs—especially if your team depends on certain Git-exclusive features—Jujutsu’s approach to branching, committing, and merging can drastically streamline everyday tasks. If you’re feeling the limitations of Git, exploring Jujutsu might just open new doors in your development workflow.