Skip to main content

Yet another Jujutsu tutorial

38 mins

This is a step-by-step tutorial on how I use Jujutsu (jj) in my daily development workflow. The tutorial is split into two parts:

  1. Starting from scratch: Sets up a new repository from scratch using jj
  2. Iterative changes: Showcases the workflow of daily steps working on feature branches

Workflow Overview #

Examples shown in this tutorial resemble my specific workflow, but you will find most of them are pretty common and can be adapted to yous specific needs.

My regular workflow can be summarized as:

  1. Work in short-lived feature branches (feat, fix, chore, etc.)
  2. Push new commits to the feature branch to address PR feedback
  3. Squash-merge feature branches to main via pull requests
  4. Rebase your local local main branch
  5. Rebase feature branches from main

The git history for a given feature branch usually looks like:

gitGraph commit id: "Initial commit" commit id: "Add new test.txt file in branch" commit id: "Add 'hello world' to test.txt " branch feature checkout feature commit id: "Update documentation in branch" checkout main commit id: "Update documentation in main" merge feature tag: "squash merge"

Commit signing #

This should work without needing to configure anything in jj. Since jj uses git as backing repo, the commit signing configuration is propagated when changes are committed as long as you have configured the commit signing for your user in the local git configuration.

Glossary #

ConceptDescription
Working copyJujutsu doesn’t have a concept of a staging area like git thas. In Jujutsu, every and any change to the repository is automatically tracked and commited every time you run a jj command. This ensures no change goes unnoticed and saves you the process of adding changes to the staging area and then committing them
Commit IDUnique identifier for a commit. Displayed in standard hexadecimal format at the end of the jj log output. Each commit corresponds to a snapshot of the repository’s state at a specific point in time, and the commit ID changes whenever the commit is modified
Change IDUnique identifier for a change in Jujutsu, which is a mutable object that can evolve over time. Unlike a commit ID, the change ID remains constant even as the underlying commit (snapshot) it refers to changes. Each change can refer to multiple commits due to its mutable nature, allowing users to modify the change without altering its ID
RevisionIn Jujutsu, the term revision is synonymous with commit. It refers to a particular state of the repository captured at a specific point in time
Immutable RevisionsThey refer to the snapshots of commits that cannot be altered once they are created
@Refers to the current revision or working copy
@-Refers to the parent revision or previous revision

Installation #

The package is available in brew for macOS users. For Linux and Windows users check out the official installation instructions.

brew install jj

Verify Installation #

jj --version

Starting from scratch #

I’m starting to use jj in an existing repo with a long commit history in my organization; this section shows you how to set up jj in a repository (new or existing) and makes a couple of commits so that I can mimic the workflow I use at work; commands explained in this section will not be re-introduced in the next one, but I’ll provide references to both this section and the cheatsheet across the tutorial so you can easily pick where you want to start off without missing important details.

Initialize Jujutsu in a new repository #

First of all, create a new folder where you want to setup jj. I will assume that this is an empty folder, that has not already been initialized either with git or jj.

# Create a project folder
$ mkdir jujutsu-tutorial

# Navigate to the project
$ cd jujutsu-tutorial

# Initialize Jujutsu in the current working directory
$ jj git init
# jj git init --colocate # <- Equivalent version of the same command. The --colocate flag is used by default with the `init` command
Initialized repo in "."
Hint: Running `git clean -xdf` will remove `.jj/`!

# Check the contents of the project. You should now have .git and .jj folders
$ ls -la
total 0
drwxr-xr-x@ 4 mac  staff  128 Jan  8 09:48 ./
drwxr-xr-x  5 mac  staff  160 Jan  8 09:48 ../
drwxr-xr-x@ 9 mac  staff  288 Jan  8 09:48 .git/
drwxr-xr-x@ 5 mac  staff  160 Jan  8 09:48 .jj/
  • jj git init created a .jj/ directory alongside a .git/ directory: The .git directory will serve as backend for jj, while also allowing you to execute git commands in the same folder
  • Running jj git init is equivalent to running jj git init --colocate, jj git init . or jj git init . --colocate
jj git init --no-colocate - Only initialize the jj repo without a backing .git directory. This won’t allow git commands to work in the same folder

Alternative: Clone an existing repository with jujutsu #

You can clone an existing repo using jj with the following commands, however for simplicity this tutorial will continue assuming you’re starting a new repo from scratch. If you already have a repo you’d like to continue the tutorial with, make sure you go through the Basic repo configuration steps first, and then you can skip over to the Iterative changes section.

# Clone a Git repository using Jujutsu
$ jj git clone https://github.com/your-company/your-project.git
$ jj git clone https://github.com/your-company/your-project.git my-project

Basic repo configuration #

Let’s now configure your user details.

Global configuration #

This configuration will be applied to all repos using jj.

# Configure your user name
$ jj config set --user user.name "csepulveda-dev"
Warning: This setting will only impact future commits.
The author of the working copy will stay " <>".
To change the working copy author, use "jj metaedit --update-author"

# Configure your user email
$ jj config set --user user.email "dev@camilosep.com"
Warning: This setting will only impact future commits.
The author of the working copy will stay " <>".
To change the working copy author, use "jj metaedit --update-author"

# Configure your preferred editor
$ jj config set --user ui.editor "vim"

# Verify the values set
$ jj config g user.name
csepulveda-dev
$ jj config g user.email
dev@camilosep.com
$ jj config g ui.editor
vim

Alternatively, you can open the config file and edit the values directly.

$ jj config edit --user
#:schema https://docs.jj-vcs.dev/latest/config-schema.json

[user]
name = "csepulveda-dev"
email = "dev@camilosep.com"

[ui]
editor = "vim"

Local configuration #

If you have multiple repos each with different git users, you can specify the jj configuration per repo instead of globally by replacing the --user flag with --repo.

# Configure your user name
$ jj config set --repo user.name "csepulveda-dev"
Warning: This setting will only impact future commits.
The author of the working copy will stay " <>".
To change the working copy author, use "jj metaedit --update-author"

# Configure your user email
$ jj config set --repo user.email "dev@camilosep.com"
Warning: This setting will only impact future commits.
The author of the working copy will stay " <>".
To change the working copy author, use "jj metaedit --update-author"

# Configure your preferred editor
$ jj config set --repo ui.editor "vim"


# Verify the values set
$ jj config g user.name
csepulveda-dev
$ jj config g user.email
dev@camilosep.com
$ jj config g ui.editor
vim

Same as with global configuration, you can replace the flag to open the local config file.

Alternatively, you can open the config file and edit the values directly.

$ jj config edit --repo
#:schema https://docs.jj-vcs.dev/latest/config-schema.json

[user]
name = "csepulveda-dev"
email = "dev@camilosep.com"

[ui]
editor = "vim"

Checking the status of your repo #

Let’s see how you’re repo is looking so far. The next command is the jj equivalent to git status.

# Check the current status of your repository
$ jj st
# jj status # <-- Verbose version of the command above
The working copy has no changes.
Working copy  (@) : vmmslvxz 13ac4b48 (empty) (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)

This command prints a lot of new information, quite different from what we’re used to with git status, and introduces a few new concepts:

OutputDescription
“The working copy has no changes”Shows the current changes to files in the working copy
“Working copy (@) : vmmslvxz 13ac4b48 (empty) (no description set)”Shows the Change ID, Commit ID, and description of changes of the working copy.
- vmmslvxz: Represents the Change ID
- 13ac4b48: Represents the Commit ID
- (empty): Represents the working copy has no changes
- (no description set): Represents the working copy does not have a description/message
“Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)”Shows the Change ID, Commit ID, and description of changes of the parent revision. Since we don’t have a parent revision (this is a brand new repo with no previous commits) the parent shows up with the IDs zzzzzzzz 00000000

One thing that has helped me switch gears from git to jj is to think that in jj you commit first, and add changes later; that’s why even though you have done nothing other than initialize jj, you already have commited, empty changes when running jj st. In other words:

  • in git, I’m making changes now and commit them later
  • in jj, I’m commiting now and then amending the commit with changes

Check this command on my cheatsheet

Tracking changes #

Right now we don’t have any changes, let’s add some and see what happens.

$ touch calculator.sh

$ echo '#!/usr/bin/env bash

    # Simple 2-argument calculator
    # Usage:
    #   ./calc.sh -a 3 4    # addition

    set -euo pipefail

    usage() {
        echo "Usage: $0 {-a} <num1> <num2>"
        echo "  -a  addition"
        exit 2
    }

    if [ "$#" -ne 3 ]; then
        usage
    fi' >> calculator.sh
$ chmod +x calculator.sh
$ jj st
Working copy changes:
A calculator.sh
Working copy  (@) : vmmslvxz acc2c5ca (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)

We’re adding the scaffolding for a simple calculator in bash. Right now it doesn’t do much, just prints the usage, but it serves us well to see some changes in the jj st output:

  • The working copy now shows A calculator.sh, indicating the calculator.sh file has been Added. Every change creates a commit; No “dirty” working directory.
  • The commit ID has changed from 13ac4b48 to acc2c5ca: Everytime you run a jj command (like jj st), the first action jj takes is to check and commit changes made to the working copy; that’s why the commit ID has changed.
  • The change ID remains the same (vmmslvxz): We’re still working on the same change or revision. Change ids can change, but we’ll get into that later.

So we didn’t need to add the file to a staging area, and we didn’t need to commit the file, jj took care of that for us, which is good because every change we make to the repo will be tracked, recoverable, and auditable by jj.

We still need a way to give descriptions to the changes.

$ jj desc -m "Added calculator.sh file with usage"
# jj describe --message "Added calculator.sh file with usage" # <-- Verbose version of the command above
Working copy  (@) now at: vmmslvxz b6317ee6 Added calculator.sh file with usage
Parent commit (@-)      : zzzzzzzz 00000000 (empty) (no description set)

Great! Now we have some changes and we have added a description for them. If you have been a diehard git user for a long time and this is the very first time you are seeing jj in action you may be having a little stroke (I know I did) because of the difference in paradigm between jj and git.

But how cool is this? You can start working on your changes right away and then give a description to them without worrying about anything else. Your changes will be tracked and commited automatically every time you run jj st.

Check this command on my cheatsheet

jj describe -m <message> command adds or amends the message for the current revision; notice that the commit ID changed from acc2c5ca to b6317ee6.

Creating new revisions #

Now that we’re done with the current changes, I’d like to add the addition logic for our calculator in a different revision. How can I do that? There’s a few different ways we can achieve that, but let’s start with the easiest way to do it.

$ jj new
Working copy  (@) now at: opkywnkv 3fbe947a (empty) (no description set)
Parent commit (@-)      : vmmslvxz b6317ee6 Added calculator.sh file with usage

jj new creates a new empty revision on top of your current working copy (@). We can see that the working copy is now pointing to a new revision with change ID opkywnkv and commit ID 3fbe947a. The previous revision is still there, and has become the parent (@-) of your current working copy; notice that the change ID and commit ID of the parent revision have not changed in this case.

Check this command on my cheatsheet

You can now follow the exact same steps that we did before to add new changes, describe them, etc, but let’s spice things up a bit with new commands.

$ jj new -r @-
Working copy  (@) now at: ynkqnwop 6c3d1784 (empty) (no description set)
Parent commit (@-)      : vmmslvxz b6317ee6 Added calculator.sh file with usage

What just happened? Our working copy is still one revision ahead of our previous changes, but the change and commit IDs have changed, even though we haven’t modified any file.

  • this command created a new revision on top of the previous one; by using the -r flag we can specify jj on top of which revision we want to create a new one
  • @- references the previous revision to the working copy, so jj created a new revision that replaced the previous, empty, one (opkywnkv 3fbe947a) with a new change and commit ids
  • the previous revision (opkywnkv 3fbe947a) was replaced altogether because it was empty

I’m getting a bit tired of using jj new and then jj desc to create and describe changes in separate commands though.

$ jj new -r @- -m "Add addition logic to calculator"
Working copy  (@) now at: nltmulpq 646dc053 (empty) Add addition logic to calculator
Parent commit (@-)      : vmmslvxz b6317ee6 Added calculator.sh file with usage

Perfect! Now we can create revisions with descriptions using the same command. You have to think ahead, though, about what you plan to do in this new revision so that you can pass an appropriate description; but let’s see how you can amend descriptions in case you need to.

I don’t like that I used ‘Added’ for the first revision’s description and ‘Add’ for the current one, so let’s update the previous revision message with “Add calculator.sh file with usage”.

$ jj desc -r @- -m "Add calculator.sh file with usage"
Rebased 1 descendant commits
Working copy  (@) now at: nltmulpq 824eb92c (empty) Add addition logic to calculator
Parent commit (@-)      : vmmslvxz 84d7c2db Add calculator.sh file with usage

We were able to change our previous revision description (yay) but notice that the commit ID also changed; amending descriptions will cause a new commit to be added to the revision, but the change ID should be kept as it was. Our current revision commit ID also changed, because the current revision was rebased on top of the previous one due to the fact that the commit ID of the previous one changed.

Let’s not dwell too much into rebasing for now, the point is that we were able to easily amend the previous revision message, and create a new revision with a description right away. We also learned how we can create new revisions on top of specific ones. Let’s make some changes to our calculator and move forward to another way to create revisions.

$ echo '
  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      *)
          usage
          ;;
  esac

  case "$op" in
            -a) result=$((a + b)) ;;
  esac
  echo "$result"' >> calculator.sh
$ jj st
Working copy changes:
M calculator.sh
Working copy  (@) : nltmulpq cc14b7ed Add addition logic to calculator
Parent commit (@-): vmmslvxz 84d7c2db Add calculator.sh file with usage

We have added new changes to our working copy; we can see an M in front of our filename because we have Modified an already tracked file. Again, we can also see the commit ID being updated.

$ jj cm
# jj commit # <-- Verbose version of the command above
Working copy  (@) now at: qtvylzqv ed68df41 (empty) (no description set)
Parent commit (@-)      : nltmulpq a59416b6 Add addition logic to calculator

Check that out, jj also has a commit command; this is another way to create revisions. jj cm will create a new revision on top of the current one, just like jj new does. The power, or convenience, of using jj commit instead of jj new depends on the kind of workflow you follow:

  • jj new -m <message> is most useful when you “think ahead”. You know what you’ll be working on next, so you create a new revision with a description right away
  • jj cm -m <message> is most useful when you “think backwards”. Using the -m flag will amend the current working copy with the provided , and create a new, description-less, revision on top of it. If you have previously used jj desc -m <message> to provide the description for your working copy, you can omit the -m flag.
Check this command on my cheatsheet

You may find in other tutorials that developers stick to either using jj new + jj desc or jj cm in order to create revisions, depending on their preference. I don’t do that in this tutorial, so you may see me using one or the other from this point forward. This is mainly because:

  1. I’m also learning jj while writing down this tutorial, so I will use what “feels” easier for me depending on the situation
  2. I like to keep my brain flexible while learning new tools to better grasp the inner workings instead of settling to always do the same thing the same way

Creating bookmarks #

Now we should have our calculator updated with the addition operation. This may be a good time to push our changes, but first we need to create a branch.

jj does not have a concept of “branches”; it does have the concept of “bookmarks”, which resemble git tags instead of branches.

$ jj bookmark c main -r @-
# jj bookmark create main --revision @- # <-- Verbose version of the command above
Created 1 bookmarks pointing to nltmulpq a59416b6 main | Add addition logic to calculator
$ jj st
The working copy has no changes.
Working copy  (@) : orxzrvvl e7b2d322 (empty) (no description set)
Parent commit (@-): nltmulpq a59416b6 main | Add addition logic to calculator

The jj bookmark subcommand is used to manage everything related to bookmarks. As previously explained, jj does not have a concept of branches but that doesn’t mean you can’t achieve the same things you do with git branches in jj.

jj bookmarks are references/tags/pointers/labels to specific revisions, closely resembling git tags instead of git branches. We can see in the jj st output that we have created a new bookmark called main that currently points to our previous revision (@-).

We had to set our bookmark to the parent revision because our current one is an empty revision with no changes, so there’s no point in bookmarking that one.

We’ll see the power of bookmarks later in this tutorial, for now having a main bookmark with our initial changes is good enough.

Check this command on my cheatsheet

Configuring the remote repository #

I’m happy pushing the current changes to the remote so that they can be shared with the rest of “the team”, but we need a remote repository first in order to have a place to push these changes. Adding a remote is almost identical to how you would do it in git, you just need to prepend jj to the git command.

$ jj git remote add origin "git@github.com:<username>/<repo>.git>

Tracking branches #

With our remote created, let’s push the local main branch.

$ jj git push -b main
# jj git push --bookmark main # <-- Verbose version of the command above
Error: Refusing to create new remote bookmark main@origin
Hint: Run `jj bookmark track main@origin` and try again.

Ok we got our first error, but jj is quite explanatory as to what went wrong. We’re trying to push a local bookmark, but we haven’t configured a corresponding remote bookmark in the remote. Let’s run the command jj is suggesting us to run and try again.

$ jj bookmark track main@origin
Started tracking 1 remote bookmarks.
Check this command on my cheatsheet

Pushing bookmarks #

Now that the remote branch is tracked, we can execute our push command.

$ jj git push -b main
Changes to push to origin:
  Add bookmark main to a59416b6a5a5
Enter passphrase for key '<path>':  # <-- You may not see this if you have added the remote using HTTPS instead of SSH
remote: Resolving deltas: 100% (1/1), done.
Check this command on my cheatsheet

Quick recap #

Hopefully, you’ll feel a bit more comfortable using jj now; we have covered the creation of a repository with a few commits into the main branch and pushed it to the remote to make it available to everyone else on the team. We’re ready to move on with our daily workflow of branching out feature changes and merging them back to the trunk, so let’s dive in.

Iterative changes #

This sections covers making changes to an existing repo with an existing git history. if you started the tutorial from the beginning, you already have this setup and should recognize a few jj commands; if you haven’t, you should be able to follow along this section as long as you have a repo to work on and have initialized jj in it.

Seeing the commit history #

So far we have been working adding changes on top of each other, creating a linear history. This hardly will be the case in your day-to-day job, working remotely and in collaboration with other devs.

We still have 3 operations to add to our calculator:

  • Substraction
  • Multiplication
  • Division

We’ll do that in different branches to simulate a collaborative environment, but first let’s visualize how our current commit history looks like.

$ jj log
@  orxzrvvl dev@camilosep.com 2026-01-09 11:31:46 e7b2d322
(empty) (no description set)
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
~ 
Check this command on my cheatsheet

The log shows our current revision (the working copy) and the previous revision. However, we have done more commits that the ones currently displayed. This is because the previous commits are in a straight line, so they’re hidden in the output.

Let’s list all the changes up to the current revision.

$ jj log -r '..'
# jj log --revisions '..'
@  orxzrvvl dev@camilosep.com 2026-01-09 11:31:46 e7b2d322
(empty) (no description set)
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
◆  vmmslvxz dev@camilosep.com 2026-01-09 10:37:37 84d7c2db
│  Add calculator.sh file with usage
~

Ok now we’re seeing the whole list of commits we have made.

  • The ~ symbol represents the beginning of the git history
  • The symbol represents an immutable commit. Remember how we amended commit descriptions and how doing so changed the commit ID of the revision? That is possible with mutable commits but not with immutable ones (kinda)
  • The @ symbol represents the working copy, in this case our current empty revision
  • The '..' string in the command is a revset expression. It represents all visible commits in the repo, excluding the root commit.

We can see that our history looks like a straight line, no branches whatsoever.

Creating new bookmarks #

Let’s branch off our changes, creating a new bookmark for each remaining operation of our calculator.

$ jj desc -m "Add subtract operation"
Working copy  (@) now at: orxzrvvl de0ccedc (empty) Add subtract operation
Parent commit (@-)      : nltmulpq a59416b6 main | Add addition logic to calculator

$ jj new -r main -m "Add multiply operation"
Working copy  (@) now at: soqqslyl 4b71b6f5 (empty) Add multiply operation
Parent commit (@-)      : nltmulpq a59416b6 main | Add addition logic to calculator

$ jj new -r main -m "Add divide operation"
Working copy  (@) now at: xoqxmzlt c3fdef6f (empty) Add divide operation
Parent commit (@-)      : nltmulpq a59416b6 main | Add addition logic to calculator

$ jj log
@  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
(empty) Add divide operation
│ ○  soqqslyl dev@camilosep.com 2026-01-09 13:19:33 4b71b6f5
├─╯  (empty) Add multiply operation
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 13:18:48 de0ccedc
├─╯  (empty) Add subtract operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
~
Check this command on my cheatsheet

Now we’re talking. Since we were initially positioned in an empty revision, we used jj desc to provide a description. Then, we used jj new with the -r flag to create new revisions on top of the revision bookmarked with main.

This effectively creates 3 branches in the git history, all with a common ancestor that is the revision bookmarked as main; notice that we didn’t have to use bookmarks or any branch-related-command to branch off our work.

Notice the new symbol in front of soqqslyl and orxzrvvl; this symbol represents a mutable or local commit

We can now bookmark these revisions with a descriptive name. We can do so using the change ID of each revision.

$ jj bookmark s -r o feat/add-subtract-operation
# jj bookmark set --revision orxzrvvl feat/add-subtract-operation # <-- Verbose version of the command above
Created 1 bookmarks pointing to orxzrvvl de0ccedc feat/add-subtract-operation | (empty) Add subtract operation

$ jj bookmark s -r s feat/add-multiply-operation
# jj bookmark set --revision soqqslyl feat/add-multiply-operation # <-- Verbose version of the command above
Created 1 bookmarks pointing to soqqslyl 4b71b6f5 feat/add-multiply-operation | (empty) Add multiply operation
Check this command on my cheatsheet

This is another way to create bookmarks on revisions. The bookmark set command works as an upsert, while the bookmark create works as as an insert.

I’ve avoided bookmarking the revision referencing the divide operation. We’ll get back to that one later.

You may have already noticed that when you run jj log in your terminal, some letters in the change and commit IDs will be in bold like in the screenshot below where x, s, o and n are in bold. These are shorthands that you can use in jj commands to reference the revision without specifying the entire change or commit ID.

bold-jj-log.png
Jujutsu revision highlighting

Switching the working copy #

$ jj log
@  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
(empty) Add divide operation
│ ○  soqqslyl dev@camilosep.com 2026-01-09 13:19:33 feat/add-multiply-operation 4b71b6f5
├─╯  (empty) Add multiply operation
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 13:18:48 feat/add-subtract-operation de0ccedc
├─╯  (empty) Add subtract operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
~

Our working copy is currently positioned in the xoqxmzlt revision, which corresponds to the divide operation. Let’s work on the subtract operation first.

$ jj edit feat/add-subtract-operation
Working copy  (@) now at: orxzrvvl de0ccedc feat/add-subtract-operation | (empty) Add subtract operation
Parent commit (@-)      : nltmulpq a59416b6 main | Add addition logic to calculator

$ jj log
@  orxzrvvl dev@camilosep.com 2026-01-09 13:18:48 feat/add-subtract-operation de0ccedc
(empty) Add subtract operation
│ ○  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
├─╯  (empty) Add divide operation
│ ○  soqqslyl dev@camilosep.com 2026-01-09 13:19:33 feat/add-multiply-operation 4b71b6f5
├─╯  (empty) Add multiply operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
~

We have now switched to the revision bookmarked as feat/add-subtract-operation; our working copy (@) has switched from xoqxmzlt to orxzrvvl. We leveraged the jj edit command to switch to this revision.

Since revisions can be identified with bookmarks, but also by their change ID or commit ID, we could have also used any of these IDs to switch to the desired revision.

# These are all equivalent
$ jj edit feat/add-subtract-operation
$ jj edit orxzrvvl
$ jj edit de0ccedc
$ jj edit o
$ jj edit d

Making changes in different branches #

We can now safely add the subtract logic to our calculator.

$ echo '#!/usr/bin/env bash

    # Simple 2-argument calculator
    # Usage:
    #   ./calc.sh -a 3 4    # addition
    #   ./calc.sh -s 5 2    # subtraction


    set -euo pipefail

    usage() {
        echo "Usage: $0 {-a} <num1> <num2>"
        echo "  -a  addition"
        echo "  -s  subtraction"
        exit 2
    }

    if [ "$#" -ne 3 ]; then
        usage
    fi

  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -s)
          expr="${a} - ${b}"
          ;;
       *)
          usage
          ;;
  esac

  case "$op" in
              -a) result=$((a + b)) ;;
              -s) result=$((a - b)) ;;
  esac
  echo "$result"' > calculator.sh
 
 $ jj st
Working copy changes:
M calculator.sh
Working copy  (@) : orxzrvvl 3702061d feat/add-subtract-operation | Add subtract operation
Parent commit (@-): nltmulpq a59416b6 main | Add addition logic to calculator

Remember, we don’t have to add or commit anything after making this change, jj already did it for us, so let’s switch right away to the feat/add-multiply-operation revision.

$ jj edit soqqslyl
Working copy  (@) now at: soqqslyl 4b71b6f5 feat/add-multiply-operation | (empty) Add multiply operation
Parent commit (@-)      : nltmulpq a59416b6 main | Add addition logic to calculator
Added 0 files, modified 1 files, removed 0 files

$ jj log
@  soqqslyl dev@camilosep.com 2026-01-09 13:19:33 feat/add-multiply-operation 4b71b6f5
(empty) Add multiply operation
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 14:35:02 feat/add-subtract-operation 3702061d
├─╯  Add subtract operation
│ ○  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
├─╯  (empty) Add divide operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 main git_head() a59416b6
│  Add addition logic to calculator
~

$ echo '#!/usr/bin/env bash

    # Simple 2-argument calculator
    # Usage:
    #   ./calc.sh -a 3 4    # addition
    #   ./calc.sh -m 6 7    # multiplication

    set -euo pipefail

    usage() {
        echo "Usage: $0 {-a} <num1> <num2>"
        echo "  -a  addition"
        echo "  -m  multiplication"
        exit 2
    }

    if [ "$#" -ne 3 ]; then
        usage
    fi

  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -m)
          expr="${a} * ${b}"
          ;;
      *)
          usage
          ;;
  esac

  case "$op" in
              -a) result=$((a + b)) ;;
              -m) result=$((a * b)) ;;
  esac
  echo "$result"' > calculator.sh
  
$ jj st
Working copy changes:
M calculator.sh
Working copy  (@) : soqqslyl b6021af0 feat/add-multiply-operation | Add multiply operation
Parent commit (@-): nltmulpq a59416b6 main | Add addition logic to calculator

Perfect! Now we have 2 different branches, referenced with the feat/add-subtract-operation and feat/add-multiply-operation bookmarks. Let’s push both bookmarks to our remote, and create some pull requests to merge them into main.

# Push the subtract bookmark
$ jj bookmark track feat/add-subtract-operation@origin
$ jj git push -b feat/add-subtract-operation
Changes to push to origin:
  Add bookmark feat/add-subtract-operation to 3702061d40b1
Enter passphrase for key '<path>':
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'feat/add-subtract-operation' on GitHub by visiting:
remote:      https://github.com/csepulveda-dev/jujutsu-tutorial/pull/new/feat/add-subtract-operation
remote:

# Push the multiply bookmark
$ jj bookmark track feat/add-multiply-operation@origin
$ jj git push -b feat/add-multiply-operation
Changes to push to origin:
  Add bookmark feat/add-multiply-operation to b6021af0f181
Enter passphrase for key '<path>':
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'feat/add-multiply-operation' on GitHub by visiting:
remote:      https://github.com/csepulveda-dev/jujutsu-tutorial/pull/new/feat/add-multiply-operation
remote:

pr-subtract.png
pr-multiply.png

Let’s merge the multiply PR and continue with our simulation. I will use the Squash and merge strategy because that’s how it’s done at work.

pr-multiply-merged.png

All good so far, now let’s merge the subtract PR.

pr-subtract-conflict.png

Ok, we cannot merge the subtract PR yet because understandably it has conflicts with the main branch. We need to update our local bookmark, solve the conflicts, and push the new changes to the PR so that we can merge it.

Pulling remote changes #

$ jj git fetch
Enter passphrase for key '<path>':
remote: Enumerating objects: 5, done.
remote: Total 3 (delta 1), reused 2 (delta 1), pack-reused 0 (from 0)
bookmark: main@origin [updated] tracked

We have pulled the remote changes using jj git fetch; by default, all branches tracked from the remote will be updated. In our case, the only branch in the remote with changes is the main branch.

$ jj log
@  soqqslyl dev@camilosep.com 2026-01-09 14:40:30 feat/add-multiply-operation b6021af0
│  Add multiply operation
│ ◆  ppzwlpms 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 14:50:48 main cec76b9e
├─╯  Add multiply operation (#2)
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 14:35:02 feat/add-subtract-operation 3702061d
├─╯  Add subtract operation
│ ○  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
├─╯  (empty) Add divide operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 git_head() a59416b6
│  Add addition logic to calculator
~

Now we’re seeing a couple different things in our log:

  • A new commit has showed up in the git history, ppzwlpms, referencing the merge commit created at the remote when we merged our PR
  • Our main bookmark has been automatically updated with the changes from the remote, and now points to ppzwlpms

Rebasing bookmarks onto main #

We now need to update our feat/add-subtract-operation bookmark with the latest changes from main. To do this, we’ll use the jj rebase command.

$ jj rebase -s o -o main
# jj rebase --source orxzrvvl --onto main # <-- Verbose version of the command above
# jj rebase --source feat/add-subtract-operation --onto main # <-- Equivalent version of the commands above using the bookmark name as reference
Rebased 1 commits to destination
New conflicts appeared in 1 commits:
  orxzrvvl eef28932 feat/add-subtract-operation* | (conflict) Add subtract operation
Hint: To resolve the conflicts, start by creating a commit on top of
the conflicted commit:
  jj new orxzrvvl
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
Check this command on my cheatsheet

Ok that’s a mouthful of output. But basically we’re seeing the same thing that we did in the PR, and is that there’s conflicts between our local copy of the feat/add-subtract-operation and the updated main. Resolving conflicts in jj is a little bit different from git, but luckily jj is hinting what we should do to solve them.

$ jj new orxzrvvl
Working copy  (@) now at: skrmltzx d8171a12 (conflict) (empty) (no description set)
Parent commit (@-)      : orxzrvvl eef28932 feat/add-subtract-operation* | (conflict) Add subtract operation
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
calculator.sh    2-sided conflict including an executable

Solving conflicts in jj can be summed up as:

  • Creating a new revision on top of the conflicted one
  • Solving the conflicts
  • Squashing the new revision into the conflicted one, hence solving the conflicts

Let’s see how our git history tree looks like after running jj new orxzrvvl

$ jj log
@  skrmltzx dev@camilosep.com 2026-01-09 15:09:17 d8171a12 conflict # <-- This revision is the new one created to solve the conflicts
(empty) (no description set)
×  orxzrvvl dev@camilosep.com 2026-01-09 15:05:12 feat/add-subtract-operation* git_head() eef28932 conflict # <-- This revision has a conflict with the previous one (ppzwlpms)
│  Add subtract operation
◆  ppzwlpms 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 14:50:48 main cec76b9e # <-- This is the immutable/shared commit where main is pointing at both in your local and the remote 
│  Add multiply operation (#2)
│ ○  soqqslyl dev@camilosep.com 2026-01-09 14:40:30 feat/add-multiply-operation b6021af0
├─╯  Add multiply operation
│ ○  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
├─╯  (empty) Add divide operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 a59416b6
│  Add addition logic to calculator
~
I’ve always solved conflicts manually, as in, I open the conflicted files in my editor, remove the conflict markers, leave the changes from the side I want or sometimes even merge changes from both sides, and then commit the result. I’ve tried using GUIs from different IDEs or standalone ones and I’ve always end up more confused trying to resolve conflicts that way. That’s just how my brain works.

Opening up the conflicted file reveals the jj conflict markers, which have a different syntax from the git conflict markers but are structured pretty much the same.

$ cat calculator.sh
#!/usr/bin/env bash

  # Simple 2-argument calculator
  # Usage:
  #   ./calc.sh -a 3 4    # addition
<<<<<<< Conflict 1 of 4
+++++++ Contents of side #1
  #   ./calc.sh -m 6 7    # multiplication
%%%%%%% Changes from base to side #2
+  #   ./calc.sh -s 5 2    # subtraction
+
>>>>>>> Conflict 1 of 4 ends

  set -euo pipefail

  usage() {
      echo "Usage: $0 {-a} <num1> <num2>"
      echo "  -a  addition"
<<<<<<< Conflict 2 of 4
+++++++ Contents of side #1
      echo "  -m  multiplication"
%%%%%%% Changes from base to side #2
+      echo "  -s  subtraction"
>>>>>>> Conflict 2 of 4 ends
      exit 2
  }

  if [ "$#" -ne 3 ]; then
      usage
  fi

op="$1"
a="$2"
b="$3"

case "$op" in
    -a)
        expr="${a} + ${b}"
        ;;
<<<<<<< Conflict 3 of 4
%%%%%%% Changes from base to side #1
-*)
+    -m)
+        expr="${a} * ${b}"
+        ;;
+    *)
+++++++ Contents of side #2
    -s)
        expr="${a} - ${b}"
        ;;
     *)
>>>>>>> Conflict 3 of 4 ends
        usage
        ;;
esac

case "$op" in
            -a) result=$((a + b)) ;;
<<<<<<< Conflict 4 of 4
%%%%%%% Changes from base to side #1
-            -s) result=$((a - b)) ;;
             -m) result=$((a * b)) ;;
+++++++ Contents of side #2
            -s) result=$((a - b)) ;;
>>>>>>> Conflict 4 of 4 ends
esac
echo "$result"

To solve these conflicts, we essentially want to keep both the multiply and subtract operations in the file. You can solve this manually in your favorite editor or tool, but I will just replace the entire file with the updated content.

$ echo '#!/usr/bin/env bash

    # Simple 2-argument calculator
    # Usage:
    #   ./calc.sh -a 3 4    # addition
    #   ./calc.sh -s 5 2    # subtraction
    #   ./calc.sh -m 6 7    # multiplication

    set -euo pipefail

    usage() {
        echo "Usage: $0 {-a} <num1> <num2>"
        echo "  -a  addition"
        echo "  -s  subtraction"
        echo "  -m  multiplication"
        exit 2
    }

    if [ "$#" -ne 3 ]; then
        usage
    fi

  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -s)
          expr="${a} - ${b}"
          ;;
      -m)
          expr="${a} * ${b}"
          ;;
       *)
          usage
          ;;
  esac

  case "$op" in
              -a) result=$((a + b)) ;;
              -s) result=$((a - b)) ;;
              -m) result=$((a * b)) ;;
  esac
  echo "$result"' > calculator.sh
$ jj st
Working copy changes:
M calculator.sh
Working copy  (@) : skrmltzx fd679cd7 (no description set)
Parent commit (@-): orxzrvvl eef28932 feat/add-subtract-operation* | (conflict) Add subtract operation
Hint: Conflict in parent commit has been resolved in working copy

Remember, jj automatically will add and commit your changes, so make sure you’re happy with your changes and have solved all conflicts and then just run jj squash to squash the changes from skrmltzx into orxzrvvl.

$ jj squash
Working copy  (@) now at: plrqzzuz b3a208ed (empty) (no description set)
Parent commit (@-)      : orxzrvvl ea4c1c37 feat/add-subtract-operation* | Add subtract operation
Existing conflicts were resolved or abandoned from 1 commits.

$ jj log
@  plrqzzuz dev@camilosep.com 2026-01-09 15:24:35 b3a208ed
(empty) (no description set)
○  orxzrvvl dev@camilosep.com 2026-01-09 15:24:35 feat/add-subtract-operation* git_head() ea4c1c37
│  Add subtract operation
◆  ppzwlpms 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 14:50:48 main cec76b9e
│  Add multiply operation (#2)
│ ○  soqqslyl dev@camilosep.com 2026-01-09 14:40:30 feat/add-multiply-operation b6021af0
├─╯  Add multiply operation
│ ○  xoqxmzlt dev@camilosep.com 2026-01-09 13:20:07 c3fdef6f
├─╯  (empty) Add divide operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 a59416b6
│  Add addition logic to calculator
~

Great! That wasn’t so hard. We can see that now the revision bookmarked with feat/add-subtract-operation is on top of main. We can also see there’s now an * at the end of the bookmark name. This * basically means that our local copy has changes that the remote branch does not. Let’s update our remote branch so we can merge and close our PR for the multiply operation.

$ jj git push -b feat/add-subtract-operation
Changes to push to origin:
  Move sideways bookmark feat/add-subtract-operation from 3702061d40b1 to ea4c1c3793b2
Enter passphrase for key '<path>':
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
You may be surprised that the push was not rejected by the remote. This is because the default behavior when pushing changes using jj is equivalent to using the git push --force-with-lease command in git.

pr-subtract-updated.png

Now we can merge our PR, pull the latest changes from main, and add the code for the divide operation.

$ jj git fetch
Enter passphrase for key '<path>':
remote: Enumerating objects: 5, done.
remote: Total 3 (delta 1), reused 2 (delta 1), pack-reused 0 (from 0)
bookmark: main@origin [updated] tracked

$ jj rebase -s x -o main # <-- Remember we didn't create a bookmark for the divide operation before, that's why we use the change ID for the rebase 
Rebased 1 commits to destination

$ jj edit x

$ jj log
@  xoqxmzlt dev@camilosep.com 2026-01-09 15:33:27 3bf285bf # <-- The divide operation revision is now on top of main
(empty) Add divide operation
◆  qvsuyoqz 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 15:30:58 main git_head() 9212a808
│  Add subtract operation (#1)
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 15:24:35 feat/add-subtract-operation ea4c1c37
├─╯  Add subtract operation
◆  ppzwlpms 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 14:50:48 cec76b9e
│  Add multiply operation (#2)
│ ○  soqqslyl dev@camilosep.com 2026-01-09 14:40:30 feat/add-multiply-operation b6021af0
├─╯  Add multiply operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 a59416b6
│  Add addition logic to calculator
~

$ jj bookmark s feat/add-divide-operation
Created 1 bookmarks pointing to xoqxmzlt 3bf285bf feat/add-divide-operation | (empty) Add divide operation

$ echo '#!/usr/bin/env bash

  # Simple 2-argument calculator
  # Usage:
  #   ./calc.sh -a 3 4    # addition
  #   ./calc.sh -s 5 2    # subtraction
  #   ./calc.sh -m 6 7    # multiplication
  #   ./calc.sh -d 8 2    # division

  set -euo pipefail

  usage() {
      echo "Usage: $0 {-a|-s|-m|-d} <num1> <num2>"
      echo "  -a  addition"
      echo "  -s  subtraction"
      echo "  -m  multiplication"
      echo "  -d  division"
      exit 2
  }

  if [ "$#" -ne 3 ]; then
      usage
  fi

  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -s)
          expr="${a} - ${b}"
          ;;
      -m)
          expr="${a} * ${b}"
          ;;
      -d)
          expr="${a} / ${b}"
          ;;
      *)
          usage
          ;;
  esac

  case "$op" in
      -a) result=$((a + b)) ;;
      -s) result=$((a - b)) ;;
      -m) result=$((a * b)) ;;
      -d) result=$((a / b)) ;;
  esac

  echo "$result"' > calculator.sh

Our calculator is finally complete! Let’s push this last branch and create our final PR.

$ jj bookmark track feat/add-divide-operation@origin
Started tracking 1 remote bookmarks.

$ jj git push -b feat/add-divide-operation
Changes to push to origin:
  Add bookmark feat/add-divide-operation to 047d4b57cebe
Enter passphrase for key '<path>':
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'feat/add-divide-operation' on GitHub by visiting:
remote:      https://github.com/csepulveda-dev/jujutsu-tutorial/pull/new/feat/add-divide-operation
remote:

pr-feedback.png

Looks like we’ve received some feedback that wee need to address regarding divisions by 0 and input validation. Let’s address this feedback in a separate revision.

$ jj new feat/add-divide-operation -m "Address division by 0"
Working copy  (@) now at: wzmytysl 3c56aa90 (empty) Address division by 0
Parent commit (@-)      : xoqxmzlt 047d4b57 feat/add-divide-operation | Add divide operation

$ echo '#!/usr/bin/env bash

  # Simple 2-argument calculator
  # Usage:
  #   ./calc.sh -a 3 4    # addition
  #   ./calc.sh -s 5 2    # subtraction
  #   ./calc.sh -m 6 7    # multiplication
  #   ./calc.sh -d 8 2    # division

  set -euo pipefail

  usage() {
      echo "Usage: $0 {-a|-s|-m|-d} <num1> <num2>"
      echo "  -a  addition"
      echo "  -s  subtraction"
      echo "  -m  multiplication"
      echo "  -d  division"
      exit 2
  }

  if [ "$#" -ne 3 ]; then
      usage
  fi

  op="$1"
  a="$2"
  b="$3"

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -s)
          expr="${a} - ${b}"
          ;;
      -m)
          expr="${a} * ${b}"
          ;;
      -d)
          if awk \'BEGIN{exit ARGV[1]==0}\' "$b"; then :; fi 2>/dev/null
          # Check zero safely
          if awk "BEGIN{if ($b == 0) exit 1; else exit 0}"; then
              expr="${a} / ${b}"
          else
              echo "Error: division by zero." >&2
              exit 1
          fi
          ;;
      *)
          usage
          ;;
  esac

  case "$op" in
      -a) result=$((a + b)) ;;
      -s) result=$((a - b)) ;;
      -m) result=$((a * b)) ;;
      -d) result=$((a / b)) ;;
  esac

  echo "$result"' > calculator.sh
  
$ jj new -m "Address input validation"
Working copy  (@) now at: xrpqrplu 99e06310 (empty) Address input validation
Parent commit (@-)      : wzmytysl 2257157d Address division by 0

$ echo '#!/usr/bin/env bash

  # Simple 2-argument calculator
  # Usage:
  #   ./calc.sh -a 3 4    # addition
  #   ./calc.sh -s 5 2    # subtraction
  #   ./calc.sh -m 6 7    # multiplication
  #   ./calc.sh -d 8 2    # division

  set -euo pipefail

  usage() {
      echo "Usage: $0 {-a|-s|-m|-d} <num1> <num2>"
      echo "  -a  addition"
      echo "  -s  subtraction"
      echo "  -m  multiplication"
      echo "  -d  division"
      exit 2
  }

  if [ "$#" -ne 3 ]; then
      usage
  fi

  op="$1"
  a="$2"
  b="$3"

  # simple numeric validation (allows integers and decimals, optional leading sign)
  is_number() {
      [[ $1 =~ ^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$ ]]
  }

  if ! is_number "$a" || ! is_number "$b"; then
      echo "Error: both arguments must be numbers." >&2
      exit 1
  fi

  case "$op" in
      -a)
          expr="${a} + ${b}"
          ;;
      -s)
          expr="${a} - ${b}"
          ;;
      -m)
          expr="${a} * ${b}"
          ;;
      -d)
          if awk \'BEGIN{exit ARGV[1]==0}\' "$b"; then :; fi 2>/dev/null
          # Check zero safely
          if awk "BEGIN{if ($b == 0) exit 1; else exit 0}"; then
              expr="${a} / ${b}"
          else
              echo "Error: division by zero." >&2
              exit 1
          fi
          ;;
      *)
          usage
          ;;
  esac

  case "$op" in
      -a) result=$((a + b)) ;;
      -s) result=$((a - b)) ;;
      -m) result=$((a * b)) ;;
      -d) result=$((a / b)) ;;
  esac

  echo "$result"' > calculator.sh

Great, we have addressed all the feedback, let’s push our changes and ask for a new revision.

$ jj git push -b feat/add-divide-operation
Bookmark feat/add-divide-operation@origin already matches feat/add-divide-operation
Nothing changed.

Wait what? This is jj saying there are no new changes to push, but why is that?

$ jj log
@  xrpqrplu dev@camilosep.com 2026-01-09 15:51:27 f861665d
│  Address input validation
○  wzmytysl dev@camilosep.com 2026-01-09 15:49:47 git_head() 2257157d
│  Address division by 0
○  xoqxmzlt dev@camilosep.com 2026-01-09 15:41:16 feat/add-divide-operation 047d4b57
│  Add divide operation
◆  qvsuyoqz 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 15:30:58 main 9212a808
│  Add subtract operation (#1)
│ ○  orxzrvvl dev@camilosep.com 2026-01-09 15:24:35 feat/add-subtract-operation ea4c1c37
├─╯  Add subtract operation
◆  ppzwlpms 129193571+csepulveda-dev@users.noreply.github.com 2026-01-09 14:50:48 cec76b9e
│  Add multiply operation (#2)
│ ○  soqqslyl dev@camilosep.com 2026-01-09 14:40:30 feat/add-multiply-operation b6021af0
├─╯  Add multiply operation
◆  nltmulpq dev@camilosep.com 2026-01-09 11:31:46 a59416b6
│  Add addition logic to calculator
~

The log does show our latest changes. But wait. The bookmark is still pointing to the initially pushed revision.

In order to push new changes from a bookmark, you need to re-position the bookmark so that it points to the latest revision you mean to push. This won’t be done automatically by jj.

$ jj bookmark s feat/add-divide-operation
Moved 1 bookmarks to xrpqrplu f861665d feat/add-divide-operation* | Address input validation

Now our local bookmark has been updated to the latest revision. We should be good to push our changes now.

$ jj git push -b feat/add-divide-operation
Changes to push to origin:
  Move forward bookmark feat/add-divide-operation from 047d4b57cebe to f861665d971c
Enter passphrase for key '<path>':
remote: Resolving deltas: 100% (2/2), completed with 1 local object.

pr-divide-merged.png

Final remarks #

We did it! 🎉 This is the usual workflow I use at work, creating feature branches, updating them, rebasing them, etc. I hope this tutorial may help you adopt jj into your workflow, or at least provide a 10k feet view of how you can achieve the same behavior you’re used to with git using jj.

Acknowledgements #

Of course, this tutorial is of my own creation, but I’d be lying if I say I downloaded jj and followed the man pages to even start getting a grasp of the features. I did my own research and read other tutorials found online, that I recommend you take a look at too, or if you found this tutorial very basic or you just didn’t like it, maybe some of the followings will suit you better so do pay them a visit: