name: jj-megamerge
description: Workflow for jujutsu (jj) megamerges — local-only octopus merges that integrate multiple working branches. Use when the user mentions megamerges, octopus merges, parallel branches, or when jj log shows a commit described megamerge or @ has a merge in its ancestry.
jj Megamerge Workflow
A megamerge is a local-only octopus merge that combines all the user's active feature branches into one integrated working state. The user works in an empty WIP commit above the megamerge, then routes hunks back to individual branches with jj absorb. Only the individual branches are pushed — never the megamerge itself.
Reference: https://isaaccorbrey.com/notes/jujutsu-megamerges-for-fun-and-profit
Detect that a megamerge is in play
Run before any non-trivial jj operation:
jj log -r 'closest_merge(@)' --no-graph -T 'description ++ "\n"'
If the description is megamerge (or output is non-empty and the merge is mutable), you are in the megamerge workflow. Other tells:
jj logshows a commit labeledmegamergewith 3+ parents@is empty with description(no description set)and parent is the megamergeclosest_pushable(@)resolves to the megamerge itself (suspicious — verify before pushing)
Hard rules
- Never push a megamerge. It's in
git.private-commitsandjj git pushwill refuse. Do not pass--allow-privateto override. - Never edit
@if@IS the megamerge. Always work in the empty WIP commit above. If@is the megamerge, runjj newfirst. - Always run
jj git push --dry-runbefore the real push. Confirm only the intended branches move. - Don't fold the megamerge into a real merge. Don't
jj describethe megamerge to look like a normal merge commit — that defeats the private-commits filter.
Available aliases
Configured in ~/.config/jj/config.toml:
| Alias | Action |
|---|---|
jj mega <bookmark>... |
Create octopus merge from N bookmarks, leave empty WIP commit on top |
jj insert <rev> |
Place <rev> between trunk() and closest_merge(@) (becomes new merge parent) |
jj stage |
Move every non-empty commit above the megamerge into it as new parents |
jj restack |
Rebase mutable roots onto trunk() (lifts megamerge + branches together after jj git fetch) |
Plus the built-in jj absorb for routing hunks.
Routing changes back to branches
After editing files in the WIP commit:
jj absorb # auto-routes hunks to the ancestor commit that last touched those lines
If a hunk can't be auto-placed (new file, unrelated context), it stays in @. Route manually:
jj squash --to <branch-tip-rev> --interactive # only if user is driving — interactive
jj squash --to <branch-tip-rev> <files> # non-interactive, scoped by file
For brand-new work that should become its own branch:
# in the WIP commit
jj describe -m "feat-newthing: ..."
jj stage # inserts as new megamerge parent
jj bookmark create feat-newthing -r @- # @- is the now-staged commit
Updating against trunk
jj git fetch
jj restack
The megamerge and all branches lift onto the new trunk. Conflict resolution happens once at the megamerge level rather than on every branch.
Pushing
jj git push --dry-run # ALWAYS first
jj git push --bookmark feat-auth # one branch
jj git push --bookmark 'feat-*' # glob
Never jj git push -c <megamerge-rev> or jj git push --all without inspecting the dry-run.
Tearing down
The megamerge is just a commit. To dissolve it without touching branches:
jj abandon -r 'closest_merge(@)::' # abandons megamerge + everything above
Branches remain untouched. Recreate later with jj mega ....
When to ask the user
- The user wants to push and dry-run shows the megamerge would be pushed → stop, surface the issue, do not pass
--allow-private. jj absorbleaves significant residue in@and you can't tell which branch a change belongs to → ask before squashing.- The megamerge has conflicts after
jj restack→ resolve in@'s ancestors carefully; neverjj abandonthe megamerge as a shortcut.