Rebasing
Now that Bob is happy with his second commit, he tries to push it to the remote:
jj new
jj bookmark move main --to @-
jj git push
As you might've already guessed, Bob gets the same error message as Alice did earlier. He also decides to follow the hint of fetching from the remote (after watching some cat videos):
jj git fetch
Here's what Bob's log looks like now:
@ ztyvwqll bob@local 2025-07-22 21:29:52 93f27644 │ (empty) (no description set) ○ smswwyok bob@local 2025-07-22 21:29:27 main?? main@git git_head() e583fa6e │ Add submission instructions │ ◆ twywpklt alice@local 2025-07-22 21:27:24 main?? main@origin 606959ce ╭─┤ (empty) Combine code and documentation for hello-world │ │ │ ~ │ ◆ quolxwkk bob@local 2025-07-22 21:22:22 8d538390 │ Document hello.py in README.md ~
It's basically the same situation. Bob's and Alice's changes have branched-off into different directions. To merge them back together, Bob could do the same thing Alice did and create a merge commit. However, Bob doesn't like merge commits. Preferring straight lines in his log, he decides to take a different approach from Alice.
Bob is going to pretend like he made his changes on top of Alice's changes all along. His most recent commit should have Alice's commit as its parent, because that will result in a linear history.
There is a Jujutsu command just for this purpose:
It's called rebase
.
As the name implies, it takes commits from one "base" (some ancestor) and moves them on top of a different "base".
jj rebase
on its own doesn't work though, it needs to know the destination of the operation.
In this case, Bob wants to move his commit on top of the state of the remote main
bookmark, i.e. Alice's commit.
Therefore, he runs the command:
jj rebase --destination main@origin
What does the log say?
@ ztyvwqll bob@local 2025-07-22 21:30:38 e911e4c7 │ (empty) (no description set) ○ smswwyok bob@local 2025-07-22 21:30:38 main* git_head() 02bd1179 │ Add submission instructions ◆ twywpklt alice@local 2025-07-22 21:27:24 main@origin 606959ce │ (empty) Combine code and documentation for hello-world ~
Splendid, that's exactly what Bob wanted.
Jujutsu even figured out that the main
bookmark should probably point to Bob's new commit.
All that's left to do is to rerun:
jj git push
What Bob has just done is a paradigm shift: He has rewritten history. The history used to say that Bob's second commit descended from his first one, but now it says it descended from Alice's commit. The extent of this revisionism is still benign, but as we progress through the book, we will explore the dark arts of history manipulation ever more deeply.
...ahem, where were we?
I've glossed over a small detail.
I said the rebase
command moves commit from a one base to a new one.
What is that previous base?
When using the --destination
flag (or -d
for short), Jujutsu will select the first shared ancestor of your working copy and the new base as the old base to rebase from.
In simple terms, the rebase
command moves only those commits that aren't on the destination branch yet.
In our example, there was only one commit to move.
Creating a merge commit and rebasing are both valid ways of recombining changes that branched-off into different directions. They both have advantages and disadvantages. Some people care more about one aspect than another, so they end up having strong opinions about which approach is best. Here's a hopefully balanced overview of the main trade-off:
advantage | disadvantage | |
---|---|---|
merge | Preserves the history exactly as it happened. | Can result in a tangled, hard-to-read history. |
rebase | Results in an easy-to-read, linear history. | Lies about the order in which things happened. |
Once you have determined the correct opinion about which one is better, please let everybody on the internet know about your important discovery!