A Short Guide to Jujutsu (Jj) for Git Users

Jujutsu (jj) is a modern, Git-compatible version control system from Google designed to be simple and powerful. It aims to make complex workflows effortless, especially cleaning up commit history. If you’ve ever felt that Git was hard or required too much discipline, jj might be for you.

The jj Philosophy

  • Effortless History: With jj, you don’t need perfect commits from the start. Work freely, and clean up your history later with powerful commands like jj split, jj squash, and jj edit.
  • No Staging Area: The working copy is always “live.” There is no need for git add or git stash. To work on something new, you just check out a different revision.
  • Free Checkpointing: jj records every operation. You can undo almost anything with the magic jj undo command.

1. Installation and Configuration

First, install jj on your system.

  • Linux/macOS (Homebrew):
    1
    
    brew install jj
  • Windows (Scoop/Winget):
    1
    2
    3
    
    scoop install jj
    # OR
    winget install --id Jujutsu.Jujutsu
  • NixOS:
    1
    2
    3
    
    environment.systemPackages = with pkgs; [ jujutsu ];
    # OR
    nix profile install nixpkgs#jujutsu
  • For other methods, see the official installation guide.

After installing, configure your user details. This is a one-time setup.

1
2
> jj config set --user user.name "Your Name"
> jj config set --user user.email "you@example.com"

2. Creating Repositories

  • Clone a Git repo: This is the most common way to start. It creates a jj repo that is linked to the Git remote.
    1
    
    > jj git clone https://github.com/user/repo.git
  • Use jj in an existing Git repo:
    1
    2
    
    > cd my-existing-git-repo
    > jj git init --colocate
  • Create a new repo:
    1
    
    > jj git init my-new-repo

3. The Core Workflow: Making Changes

The jj workflow is different from Git’s. Instead of add -> commit, you just do the work.

  1. Start a new change: Use jj new to create a new, empty revision to work on. Your changes will be automatically saved to it.
    1
    2
    
    > jj new
    # Now, edit your files
  2. Describe your change: When you’re ready, describe the revision. You can do this at any time.
    1
    
    > jj describe -m "My new feature"
  3. Check your status: Get in the habit of running these commands all the time to see what’s happening.
    1
    2
    3
    
    > jj st      # See modified files
    > jj log     # See the history of revisions
    > jj diff    # See code changes in the current revision

4. Understanding Revision IDs vs. Commit IDs

This is the most important concept to grasp. Run jj log to see them.

  • Revision ID (e.g., wuloypwt): A local, permanent ID for a set of changes. It never changes, even if you edit the commit. It’s how you refer to revisions in jj commands.
  • Commit ID (e.g., 182d3ce4): This is the Git SHA. It represents the entire state of the worktree. It will change every time you edit the revision (e.g., by rebasing or amending the message). This is what you see in GitHub.

5. Navigating and Editing History (The Magic)

jj makes history manipulation safe and easy. There is no need for git stash.

  • Edit a past revision: Just use jj edit with the revision ID. Your working copy will update to that revision, and you can start making changes.
    1
    
    > jj edit <revision-id> # e.g., jj edit wuloypwt
    Use @ for the current revision, @- for its parent, and @+ for a child.
  • Fixing Mistakes:
    • The magic undo button: This reverts the last operation (a commit, a rebase, etc.). You can even run it multiple times.
      1
      
      > jj undo
    • The operation log: To go further back in time, view the log of all operations and restore to a previous state.
      1
      2
      
      > jj op log
      > jj op restore <operation-id>
  • Cleaning up commits:
    • Split a commit: If a commit does too much, split it into smaller ones.
      1
      
      > jj split
    • Combine commits: To merge a revision into its parent, use jj squash.
      1
      
      > jj squash

6. Branching and Remotes

In jj, branches are just pointers (called “bookmarks”) to revisions.

  1. Start a new line of work:
    1
    2
    
    # Creates a new revision on top of main
    > jj new main 
  2. Do your work: Make some edits, describe the commit.
  3. Create a bookmark (branch): Name your branch before you push. The -r @ flag means “give the name to the current revision.”
    1
    
    > jj bookmark create my-feature -r @
  4. Pull latest changes: First, fetch from the remote. Then, rebase your stack of changes onto the main branch.
    1
    2
    
    > jj git fetch
    > jj rebase -d origin/main
  5. Push your branch:
    1
    2
    
    # The --allow-new flag is needed the first time you push a new branch
    > jj git push --branch my-feature --allow-new

7. Important Considerations & Downsides

  • Force-Pushing is Normal: jj works by rewriting history, so it force-pushes changes. This can make reviewing a pull request commit-by-commit on GitHub difficult, as old commits will change.
  • State is Local: The undo history and the status of revisions are stored locally on your machine. If you clone your repo on a second computer, you won’t have the undo history from the first. A revision created on machine A will be “immutable” (you can’t use jj split, etc.) on machine B. Push your branches before switching computers to avoid losing work.
  • Using Git in Parallel: It’s safe to run read-only git commands (like git status). For any action that modifies history (commit, rebase, push), use the jj command to avoid confusion.

Also check out “The jj VCS Workshop” by Jimmy Koppel and of course the jj-vcs site

0%