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.
| Flag | Type | Default | Meaning |
|---|---|---|---|
[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, --message | string, repeatable | none | The description. Several -m values become separate paragraphs (join_message_paragraphs). |
--no-edit | bool | false | Create the commit but leave the working copy where it is. --edit is a hidden no-op partner flag. |
-A, --insert-after (alias --after) | revsets, repeatable | none | Make 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, repeatable | none | Put 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
| Key | Default | Effect |
|---|---|---|
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 page | Only matters with -A/-B: the children that get rebased must be mutable. |
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
Parent commits (tree, parents) | read | Loaded to merge their trees, and the first parent's parents are checked for advanceable bookmarks. | commits |
| New commit | write | Fresh change ID, the parents in the given order, the merged tree, the description, author and committer from settings. | commits |
Child commits (with -A/-B) | rewrite | Each gets new parents and is rewritten, recording the old commit as its predecessor. Their descendants are rebased too. | transactions |
View.head_ids | write | The new commit becomes a head, and rewritten children replace their old versions. | view |
View.wc_commit_ids | write | This workspace's entry is set to the new commit (not with --no-edit). | view |
View.local_bookmarks | write, rarely | Only advanced bookmarks move. Bookmarks on rewritten children follow them. | view |
| Operation and its metadata | write | One operation, description new empty commit. | operations |
Working copy (TreeState) | read, then write | Snapshotted 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 (viarebase_descendants), and jj printsRebased N descendant commits. - Working copy: snapshotted when the workspace is loaded. Afterwards
MutableRepo::editmoves 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 defaultjj new, the old@is the new commit's parent, so it isn't a head and is kept. jj undorestores the previous operation's view: the new commit stops being a head (it disappears fromjj 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:
| Given | Parents | Children to rebase |
|---|---|---|
revisions (or nothing, meaning @) | the revisions | none |
-A X | X | all current children of X (X.children()) |
-B Y | the parents of Y, in Y's own parent order | Y |
-A X -B Y | X | Y |
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
| Message | When | Source |
|---|---|---|
No revisions found to use as parent | The 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 commit | A child given with -B is an ancestor of a parent given with -A. | cli_util.rs |
Commit … is immutable / The root commit … is immutable | A 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 conflict | Positional 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.
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.