Marcus Kazmierczak

Home mkaz.blog

Jujutsu VCS - Getting Started Guide

Jujutsu (jj) is a new version control system that's been picking up a fair amount of interest. While Git has dominated for nearly two decades, Jujutsu promises a simpler, more intuitive approach to version control.

Table of Contents

Do we really need a new VCS?

I'll be honest: I was skeptical. A VCS claiming to be "so much easier and more powerful than Git"? Git works fine for most daily workflows, I don't find the command-line all that complex, and the advanced scenarios tend to be rare.

Funny thing, years ago I was equally skeptical of a new VCS when I wrote the post "Don't be a git, just use subversion". In it I argued that Git's complex branching and non-linear nature wasn't worth it for most developers. Here's the Hacker News discussion calling me an idiot.

I'm not a complete luddite. I jumped from CVS to Subversion immediately when it launched.

So, learning from my previous bad take and seeing Jujutsu gaining some traction, I decided to give it a fair shot.

The verdict: I'm a convert. It took me a few weeks to grok it, but now it is all I use.

Getting Started

Most Jujutsu tutorials dive straight into advanced features, but let's start with the essentials. Here's how to try it alongside your existing Git projects without disruption.

Installation and Setup

Install Jujutsu:

brew install jj  # macOS
# or your package manager of choice

Initialize in an existing Git repo:

jj git init --colocate

This creates a Jujutsu repository that coexists with your Git repo. You can switch between both tools seamlessly.

Note: The examples use inline messages using -m "Comment" just like in git, if you omit it will open your EDITOR and enable full comments. Strive to write better commit messages than these examples.

Automatic Staging

In Git, you make changes, add them to a staging area, and then commit; in Jujutsu you create a working space first, then any changes made are automatically included until you make a new working space.

My Daily Workflow

Here's the pattern I've settled into:

1. Create a new working space

jj new

2. Describe what you're working on

jj desc -m "Add user authentication feature"

Note: This can be modified anytime, I like to describe early to focus the change

3. Make your changes

  • Files are automatically staged as you modify them
  • Everything respects your .gitignore file
  • If something unwanted gets added: jj file untrack [FILE]

4. Start the next change

jj new  # Previous working space becomes a commit

That's it. Your previous working space automatically becomes a commit in both Jujutsu and Git.

Real Example: Adding This Article

Let me show you exactly how I used Jujutsu to add this article to my blog:

Step 1: Create empty working space

jj new

Aside - Explaining the status message

The jj status output looks a bit gnarly and has more than a typical git status or log. This shows two lines, the working copy and the parent. Explaining the working copy line:

  • @ symbol represents "here" - your current position in the commit history. This can be used as a reference, the @- means the item before @
  • ymzurour is the change ID, a unique identifier Jujutsu assigns to track this set of changes. This will not change regardless of your edits to that change set.
  • The 7a959934 is the git commit hash of this change in the repo, this will change as you make changes in the changeset.
  • (empty) - No files have been modified in this working copy.
  • (no description set) - No commit message has been written yet.

Step 2: Create article which gets automatically staged:

Step 3: Add description

jj desc -m "Add jujutsu article"

Step 4: Create next working space

jj new

The previous working space is now a proper Git commit (verified with git log):

Note: Pay attention to the Jujutsu change ID which stays the same throughout all the changes above, and the git commit hash which changes with each change until the next working space is created.

After two more changes (adding images and updating the home page), here's what jj log shows:

The real power here is that you can go back and edit any previous commit with jj edit [changeID] or reorder them with jj rebase. But let's keep things simple for now.

Jujutsu supports creating a new space and describing in one step using jj new -m "Description". You can use this as a shortcut instead of two commands: jj new and then jj desc -m "".

Working with Bookmarks (Git Branches)

Jujutsu calls Git branches "bookmarks" – they work similarly, but with some key differences that make the distinction worthwhile. I'll call it out below.

In the log above, you'll see:

  • trunk: Your local branch
  • trunk@origin: The remote branch

I started with a local commit that hadn't been pushed yet, which is why they're different.

Creating a Feature Branch

To move these commits to a feature branch, create a bookmark pointing to the specific change:

jj bookmark set -r tylvvqpn add/jujutsu

Note: I could use jj bookmark set -r t add/jujutsu with just the t to refer to the change id, since that is the shortest unique string to refer. This is why the color syntax highlights the first letter or tow.

Pushing and Creating Pull Requests

jj git push -b add/jujutsu --allow-new

This pushes your bookmark to GitHub, and you'll get a link to create a PR. If you go to the repo in GitHub you will see the typical new branch PR creation prompt. You can create the PR.

Adding More Commits

To add more commits to your feature branch, use the same workflow:

  1. jj new - Create new working space
  2. Make changes
  3. jj commit -m "more updates to article" - Commit the change

One of the key differences between jujutsu and git, you aren't "on a branch" with new changes continue on that branch like in git. For jj, the bookmark points at a change id and you need to repoint your bookmark to include the new change:

jj bookmark set -r @- add/jujutsu

The @- shortcut refers to the change before your current working revision (marked by @).

Now your local bookmark differs from the remote. Push up your change:

jj git push

When your PR is done and merged on GitHub, sync your local repo:

jj git fetch

Solo Development

If you're working alone without PRs, just use the trunk bookmark:

  1. Repoint trunk to your desired revision
  2. Push to GitHub
  3. Done!

This looks like:

jj bookmark set -r CHANGE-ID trunk
jj git push

If the change id is current change use -r @, see my aliases below for how I simplify this flow.

Stash Equivalent

The stash equivalent using Jujutsu is easier than git because everything stays visible. With git stash it feels hidden away and I'll forget what I stashed and what happens when I restore.

Approach: Using jj you create a new changeset before your current working copy, make your change, and then move back to your current working copy. This looks like the following:

A simple example, I'm editing this article and want to update the footer on my site.

Current state, I created a change with description: "Update jujutsu: add stash"

To create a new changset based off the parent (not my current state) use: jj new @-.

However, this will create a fork, since both my current change (updating the article), and my new change (updating the footer) will have the same parent. This is fine, but will require resolving later.

So to avoid that extra step later, I create the fix changeset to be created after the parent using: jj new -A @- -m "Update footer"

You can see the new change was added after the previous parent, and before current change. I can now make an update to the footer.

And when I'm done go back to editing the new article using: jj edit tltlyrpq

I could then mark that change as trunk and push up to deploy.

jj bookmark set -r yvwynsxy trunk
jj git push

Split and Squash

Split and Squash are two commands in Jujutsu to modify changes. Split will break a change into multiple changes, and squash does the opposite takes multiple changes and squashes into one.

Split

Here's an example on how to use jj split to break a change into multiple changes.

If you don't specify a change, jj split will start an interactive TUI. In that interface, the files marked will become a new change, those unmarked will stay in the current.

In a similar scenario to the stash above, let's say I made a change to my base template while I was in the middle of editing this article. I forgot to create a new change for it first. This jj status shows both files have changes:

So if I run jj split it will launch the TUI, I will select the file I want to split first (the change to the base template) by tapping space bar, and then press C to confirm.

After the split, you can see this jj log shows there are now two different changes:

Squash

Use jj squash to take multiple changes and merge them. By default, jj squash will take the current commit @ and squash into its parent.

Here's an example with two commits, one name father, one named son.

Running: jj squash -m "I am father and son will squash the son into the father. If you don't specify the message, it will open your EDITOR to add.

So now after, jj status shows just the single commit:

Useful Tips and Configuration

Quick Commit Shortcut

Instead of jj desc followed by jj new, use:

jj commit -m "Your description"

Increase File Size Limit

Jujutsu's default 1MB file limit is restrictive. Update ~/.config/jj/config.toml:

[snapshot]
max-new-file-size = "10MiB"

Essential Recovery Commands

  • jj undo - Undo the last operation
  • jj abandon [changeID] - Remove a change
  • jj log - See your change history
  • jj status - Current working space status

Aliases

Create aliases in the config file ~/.config/jj/config.toml

Here are my aliases. They are basically just shortcuts, since all fetches, inits, and pushes are git fetches, git inits, and git pushes, just remove the git part. Likewise for track, all tracks are bookmark tracks.

[aliases]
fetch = ["git", "fetch"]
init = ["git", "init", "--colocate"]
mark = ["bookmark", "set", "-r", "@"]
push = ["git", "push", "--allow-new"]
track = ["bookmark", "track"]

With the aliases in place my workflow to make a change on trunk is:

jj new        # create new space if it doesn't exist
# ... make changes ...
jj desc -m "Describe change"
jj mark trunk # mark current rev as trunk
jj push       # push up change

The push will update the remote and create the local as immutable and create a new space ready to go.

Working with GitHub

Jujutsu works alongside git and GitHub, which makes it low risk to try out, you can use it yourself without having to require your team, coworkers, or anyone else to also use it. It integrates seamlessly so they won't even know.

Creating a Pull Request on GitHub

# clone the repo from GitHub
git clone git@github.com:mkaz/jotit

# track the trunk from origin
jj bookmark track trunk@origin

# ... make your changes ...

# describe your commit
jj desc -m "I did some things"

# create a local bookmark (branch)
jj bookmark set -r @ fix/things

# push branch up to GitHub
jj git push --allow-new

The push command will show the link to create a Pull Request, or you can go to the repo in your browser and GitHub will show the new branch with the option to create a pull request.

Updating Pull Request on GitHub

To update your PR on GitHub using Jujutsu is quite similar to updating trunk and pushing up. Make the change, mark the change id as the new bookmark, and push to GitHub.

# ... make changes ...

# describe your commit
jj desc -m "I made more changes"

# update bookmark to this change
jj bookmark set -r @ fix/things

# push updated branch up
jj git push
Pull in Change from GitHub

Let's suppose someone else made a change to your branch on GitHub and you want to pull in their change.

# pull in changes from GitHub
jj git fetch

# move your current state to be based off updated fix/things
jj rebase -d fix/things

Bring Local Repo Current with GitHub

After your PR, or others have been merged, here's how to bring your local repo current with GitHub.

# pull in changes from GitHub
jj git fetch

# move your current workspace to be based off trunk
jj rebase -d trunk

Summary

Here's a few areas what I like About Jujutsu, since first writing this article to try it out, I've since switched and use it 100% of the time. I'm a convert.

Less Mental Overhead

No more git add, git mv, or git rm commands. File operations just work without the extra staging. So much easier, but requires to pay attention to what is in your directory. Make sure you use .gitignore to avoid adding unwanted files.

Lightweight Commit Creation

Starting a new commit with jj new feels effortless. Since descriptions are optional and editable, there's no pressure to get everything perfect upfront.

Flexible Changes

Jujutsu's super powers. I can:

  • Edit previous commits with jj edit [changeID]
  • Split mixed changes with jj split
  • Squash related changes together

Real example: While writing this article, I made unrelated site updates. When I forgot to switch contexts, I used jj split to separate the article changes into their own commit, then squashed them with my previous article work.

Learning Strategy: Start Simple

Here's how I recommend you get started learning Jujutsu. Start small with either a side project or even create a sandbox repo just to play in. No risk.

  1. Start with the core workflow shown above
  2. Gradually add complexity as you encounter new needs
  3. Keep lifelines handy: jj undo and jj abandon for when things go wrong

The key is experimentation. It requires playing with and using to appreciate.

Note: I recommend disabling any git features in your IDE, since git is in the background for storage, it is possible git extensions can get confused or change things in unexpected ways.

Resources

Tutorials and Documentation

Getting Help

  • jj help - Built-in command reference
  • jj help [command] - Detailed help for specific commands
  • Jujutsu Discussions - Community support