jj / How jj new works

How jj new works

jj new creates a new, empty change on top of one or more parents and, by default, makes it the working-copy commit. With --insert-after or --insert-before it splices the new change into the middle of history and rebases everything above it.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. Transaction and operation
  6. 6. Algorithm and invariants
  7. Choosing parents and children
  8. The new commit's tree
  9. Rewiring the children
  10. Advancing bookmarks
  11. The description
  12. 7. Errors and edge cases
  13. 8. Worked example

In short: jj resolves the parents (default @), merges their trees into one, writes a commit with that tree and an empty (or templated) description, rebases any requested children onto it, points this workspace's working-copy commit at it, and records all of that as one operation, new empty commit. Because the new commit's tree is exactly the parents' merged tree, it has no changes of its own. That's what "empty" means here.

Everything specific to jj new lives in one function, cmd_new, plus the shared helper compute_commit_location that it shares with jj duplicate, jj rebase and others. Loading the workspace, snapshotting, and finishing the transaction are the shared lifecycle described on the CLI page.

1. Command-line surface

The arguments are NewArgs. The parents can be given positionally or with -o, but not together with -A/-B.

FlagTypeDefaultMeaning
[REVSETS]...revsets@Parents of the new change. Several revsets, or one revset naming several commits, make a merge. Hidden aliases -o, -d and -r take the same values; if both forms are given they are merged in command-line order (source).
-m, --messagestring, repeatablenoneThe description. Several -m values become separate paragraphs (join_message_paragraphs).
--no-editboolfalseCreate the commit but leave the working copy where it is. --edit is a hidden no-op partner flag.
-A, --insert-after (alias --after)revsets, repeatablenoneMake these commits the parents, and move all their current children on top of the new change. Conflicts with positional revisions.
-B, --insert-before (alias --before)revsets, repeatablenonePut the new change between these commits and their parents. Combined with -A, the -A commits become the parents and the -B commits the children.

2. Configuration

KeyDefaultEffect
templates.new_description'' (source)Used when no -m is given. If non-empty, it is rendered against a hidden draft of the new commit, so it can look at parents (the docs' example builds a "Merge X into Y" message).
templates.commit_trailers'' (source)Trailers appended to the description, but only if the description is non-empty, so a plain jj new stays discardable.
experimental-advance-branches.enabled-branches / disabled-branches[] / [] (source)Name patterns for bookmarks that move forward automatically. With the default empty list, nothing is advanced (source).
revset-aliases."immutable_heads()"see the CLI pageOnly matters with -A/-B: the children that get rebased must be mutable.

3. End-to-end flow

jj new: call flow cmd_new loads the workspace through the shared CLI lifecycle, which snapshots the working copy. compute_commit_location resolves the parent and child commits and checks that children are rewritable and that no loop would form. cmd_new collects advanceable bookmarks, starts a transaction, merges the parents' trees with merge_commit_trees, and builds the commit with MutableRepo::new_commit, filling in the description and trailers. It writes the commit, rebases each child onto it with rebase_commit, then calls rebase_descendants. Unless --no-edit is given it calls edit, which may abandon the old working-copy commit, adds the new commit as a head and sets it as this workspace's working-copy commit. It advances bookmarks and finishes the transaction as the operation "new empty commit", after which the shared lifecycle updates the working copy on disk. jj-cli jj-lib Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) compute_commit_location parents, children, rewritable, no loop revset evaluation resolve_revsets_ordered, children() get_advanceable_bookmarks start_transaction merge_commit_trees(parents) MutableRepo::new_commit → DetachedCommitBuilder, description, trailers CommitBuilder::write rebase_commit (each child) then rebase_descendants MutableRepo::edit maybe abandon old @, add_head, set_wc_commit tx.edit / advance_bookmarks skipped with --no-edit / no bookmarks tx.finish(ui, "new empty commit"): write operation, publish op head, check out the new working-copy commit (CLI page)

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Parent commits (tree, parents)readLoaded to merge their trees, and the first parent's parents are checked for advanceable bookmarks.commits
New commitwriteFresh change ID, the parents in the given order, the merged tree, the description, author and committer from settings.commits
Child commits (with -A/-B)rewriteEach gets new parents and is rewritten, recording the old commit as its predecessor. Their descendants are rebased too.transactions
View.head_idswriteThe new commit becomes a head, and rewritten children replace their old versions.view
View.wc_commit_idswriteThis workspace's entry is set to the new commit (not with --no-edit).view
View.local_bookmarkswrite, rarelyOnly advanced bookmarks move. Bookmarks on rewritten children follow them.view
Operation and its metadatawriteOne operation, description new empty commit.operations
Working copy (TreeState)read, then writeSnapshotted at the start. After the operation commits, updated to the new commit's tree, which is the same tree when there is one parent, so nothing on disk changes in the common case.working-copy

5. Transaction and operation

  • One transaction, started after the parents and children are resolved (source), finished with the description new empty commit (source).
  • Commits created: exactly one. If a description template or trailers template is in use, jj also writes a hidden draft first (write_hidden) just to render the template. It's never added to the view.
  • Commits rewritten: none by default. With -A/-B, each child and then every descendant (via rebase_descendants), and jj prints Rebased N descendant commits.
  • Working copy: snapshotted when the workspace is loaded. Afterwards MutableRepo::edit moves this workspace's @ to the new commit. The previous @ is abandoned at the same time only if it is discardable (empty with no description), is a head, and isn't referenced by a bookmark, tag or other workspace (source). With the default jj new, the old @ is the new commit's parent, so it isn't a head and is kept.
  • jj undo restores the previous operation's view: the new commit stops being a head (it disappears from jj log), @ goes back, and any rebased children and moved bookmarks return to where they were.

6. Algorithm and invariants

Choosing parents and children

compute_commit_location evaluates each revset in order into an IndexSet, so duplicates collapse and order is kept. Then:

GivenParentsChildren to rebase
revisions (or nothing, meaning @)the revisionsnone
-A XXall current children of X (X.children())
-B Ythe parents of Y, in Y's own parent orderY
-A X -B YXY

If there are children, they must all be rewritable, and none may be an ancestor of a parent, or the commit would sit in a cycle (ensure_no_commit_loop). The parent list must not be empty.

The new commit's tree

merge_commit_trees returns the single parent's tree unchanged. For several parents it merges their trees against their common ancestors and resolves what it can. Anything left unresolved stays in the tree as a conflict: jj new never fails because a merge conflicts, it creates a conflicted merge commit. Either way the new commit introduces no changes relative to its parents' merge.

Rewiring the children

For each child (source), every old parent that is also a parent of the new commit is replaced by the new commit, other parents are kept, and the new commit is added if it isn't there yet. In the -A A -B D example from the help text, D's old parent C is not one of the new commit's parents, so D ends up as a merge of C and the new commit.

Advancing bookmarks

Only when no -A/-B is given and there is exactly one parent (source): bookmarks matching the experimental patterns that point at the parent's parents move to the parent. This mirrors jj commit, so a bookmark follows the commit you just finished. It is off by default.

The description

-m wins. Otherwise templates.new_description is rendered, and if empty the description stays empty. Trailers are added only to a non-empty description, because "a commit with no description is treated in a special way in jujutsu: it can be discarded as soon as it's no longer the working copy" (source).

7. Errors and edge cases

MessageWhenSource
No revisions found to use as parentThe revsets resolved to nothing, for example jj new 'none()'.cli_util.rs
Refusing to create a loop: commit … would be both an ancestor and a descendant of the new commitA child given with -B is an ancestor of a parent given with -A.cli_util.rs
Commit … is immutable / The root commit … is immutableA commit that -A/-B would rebase is immutable, with the hint This operation would rewrite N immutable commits.cli_util.rs
Rebased N descendant commits.Status line after inserting with -A/-B.new.rs
Created new commit …Status line with --no-edit. Otherwise the new working-copy commit is printed by tx.finish.new.rs
clap argument conflictPositional revisions combined with -A/-B, or --no-edit with --edit.new.rs

Parents may be immutable (that's the usual jj new main), since they aren't rewritten. The root commit is a valid parent too.

8. Worked example

Three common uses, starting from A ← B with @ on B and a bookmark main on a side commit M (a child of X). New commits are highlighted, and primes mark rewritten commits.

jj new: before and after Row one: jj new on top of B adds an empty commit N with parent B, and the working copy moves to N. Row two: jj new @ main adds a merge commit N with parents B and M. Row three: jj new -A A adds N between A and B, and B is rebased into B prime with parent N; the working copy moves to N. jj new jj new @ main jj new -A A before after A B @ A B N @ A X B @ M main A X B M N @ main A B @ A N @ B'

In the first row the old @ (B) survives even if empty, because it is now N's parent rather than a head. In the third row B is rewritten to B' with the same change ID, and jj prints Rebased 1 descendant commits. Because -A was used, no bookmark is advanced.