Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Branching

Welcome to level 1 !

If you took a break after finishing level 0, remember that you can restore your progress in the example repo in case you lost it.

The previous level only covered situations where you are working on a project on your own. What if several people want to collaborate on the project? Let's simulate such a scenario and see what happens.

Alice and Bob are working on a group project for a computer science class. Their task is to write the classic "Hello, world!" program in Python. Alice and Bob decide to split up the work as such:

  • Alice will write the Python program.
  • Bob will add documentation to the README.

Let's start with Alice. Here's the Python program she comes up with:

print('Hello, world!')

That'll do the job just fine. She adds it to the file hello.py:

echo "print('Hello, world!')" > hello.py

Happy with her changes, Alice dutifully adds a description to her commit:

jj describe -m "Add Python script for greeting the world

Printing the text \"Hello, world!\" is a classic exercise in introductory
programming courses. It's easy to complete in basically any language and
makes students feel accomplished and curious for more at the same time."

Having completed her work, Alice creates a new commit with jj new. Here's how the repository should look at this point:

@  svplvpro alice@local 2025-07-22 21:17:41 4db2d0d0(empty) (no description set)zzywylnt alice@local 2025-07-22 21:17:31 git_head() f8e44920
│  Add Python script for greeting the world
  kxqyrwux alice@local 2025-07-22 21:14:46 main ffdf52d0
│  Add projcet description to readme
~

Next, we simulate Bob's work. He's working on a different computer than Alice, with a different copy of the repository. Since Bob is working at the same time as her, he doesn't have the commit made by Alice yet. We can simulate that by creating a third repository, which has the same remote as our primary one:

jj git clone --colocate ~/jj-tutorial-remote ~/jj-tutorial-bob

Let's go into that repo, configure our author information for the role-play and make sure the log looks the same way as when Alice started her work:

cd ~/jj-tutorial-bob
jj config set --repo user.name Bob
jj config set --repo user.email bob@local
jj describe --reset-author --no-edit
jj log
@  quolxwkk bob@local 2025-07-22 21:19:11 3ffac111(empty) (no description set)
  kxqyrwux alice@local 2025-07-22 21:14:46 main git_head() ffdf52d0
│  Add projcet description to readme
~

That looks great. So now, Bob is going to do his part the same way Alice did:

echo "# jj-tutorial

The file hello.py contains a script that greets the world.
It can be executed with the command 'python hello.py'.
Programming is fun!" > README.md
jj describe -m "Document hello.py in README.md

The file hello.py doesn't exist yet, because Alice is working on that.
Once our changes are combined, this documentation will be accurate."
jj new

Let's say that Bob was a little faster than Alice, because he was only doing the documentation. Alice was doing actual software engineering, which took a little more time. Therefore, Bob gets to update the main bookmark first:

jj bookmark move main --to @-
jj git push

A little later, Alice is also finished and now she attempts to update the main bookmark:

cd ~/jj-tutorial
jj bookmark move main --to @-
jj git push

Whoopsie! Alice is met with a scary error message in her terminal:

Changes to push to origin:
  Move forward bookmark main from cd5f3fff9c7b to e90b597ed78e
Error: Failed to push some bookmarks
Hint: The following references unexpectedly moved on the remote:
  refs/heads/main (reason: stale info)
Hint: Try fetching from the remote, then make the bookmark point to where
       ↪ you want it to be, and push again.

Alice is about to panic, so she opens social media and scrolls a little until she finds a cat video. Now that her nerves are calmed (the cat was snuggling with a bunch of baby ducks), she turns her attention back to the terminal and actually reads the error message. She is relieved to find that it actually contains useful information about what went wrong and how to fix it. Following the hint, she first fetches from the remote:

jj git fetch

fetch is a new command, it's basically the opposite of push. While push sends bookmarks and commits from the local repository to the remote, fetch downloads bookmarks and commits that someone else (like Bob!) may have pushed from another computer.

Here's the resulting jj log:

@  svplvpro alice@local 2025-07-22 21:17:41 4db2d0d0(empty) (no description set)zzywylnt alice@local 2025-07-22 21:17:31 main?? main@git git_head() f8e44920
│  Add Python script for greeting the world
│   quolxwkk bob@local 2025-07-22 21:22:22 main?? main@origin 8d538390
├─╯  Document hello.py in README.md
  kxqyrwux alice@local 2025-07-22 21:14:46 ffdf52d0
│  Add projcet description to readme
~

Oh! That's something we haven't seen before. Our version history is split into two branches. This is not unusual and lies at the core of how Jujutsu enables people to work independently from one another. But we don't know how to deal with this situation yet.

If Jujutsu allowed Alice to push the main bookmark without knowing about Bob's update, his work would accidentally get deleted. Remember: The remote only considers commits worth keeping around if they are reachable from a bookmark. But if the main bookmark points to Alice's commit, Bob's commit is not reachable anymore, because it is not an ancestor of Alice's commit.

Notice that the main bookmark appears twice in the log, both times with question marks ??. This means Jujutsu isn't sure where the bookmark should point to. Alice moved it to her commit, while it was moved to Bob's commit on the remote. Once Alice has decided where it should actually point, she can tell Jujutsu by explicitly moving it there.

Confusing terms: bookmark and branch

The terms "bookmark" and "branch" are often used in very similar situations, which can be confusing. Let's define the difference very precisely and prepare ourselves to recognize potential sources of confusion.

A bookmark is just a named label that's attached to, or points to, a single commit. That's it.

A branch is specified by its tip, which is also a single commit. However, a branch refers to the set of commits that includes the tip and all of its ancestors.

These two concepts can be combined in a phrase like this: "I pushed my changes to the main branch." This may be confusing, because main is a bookmark, right? Why are we talking about the "main" branch? Well, the main bookmark points to a commit, and that commit is the tip of the branch we mean when we say "the main branch".

There's one more thing to look out for when talking to people who primarily use Git. Git always uses the word branch for both things. The term "bookmark" carries no meaning in Git. So, when Git users say the word "branch", they may be talking about an actual branch, meaning a set of commits with a tip and all its ancestors. They may also be talking about a bookmark, meaning a named label pointing to a single commit, without attaching any significance to the ancestors of that pointed-to commit.

That's all interesting and the tree-shaped graph of our commits looks very pretty, but the teacher wants a version that includes both the program and the documentation. What should Alice and Bob do now? Find out in the following chapters!