Shikanime Org jj Convergence
Resolve the two states that block a shikanime jj repo after the tree moves
(rebase, restack, remote rewrite): conflicted revisions and divergent
changes. Reusable standalone — sks-restack hands off here after its
rebase; any skill may call this loop directly. Exit condition for both:
pushable, i.e. conflicts() and divergent() empty and bookmarks
unconflicted.
When to Use
- Any
jj log -r 'conflicts()'output after moving revisions. jj logshows a change ID twice (ab12cdef/0,ab12cdef/1, tagged(divergent)), or a bookmark rendersfeat??/(conflicted)after a fetch.jj git pushfailsWon't push commit ... since it has conflictsorError: Bookmark <name> is conflicted.- Another checkout rewrote a commit you also rewrote (stacked agents, two machines).
Prerequisites
- jj repo;
jj git fetch --remote originFIRST — divergence only becomes visible after the fetch imports the remote rewrite.
Procedure A — conflicted revisions
Locate.
jj log -r 'conflicts()' --no-graph \ -T 'change_id.short() ++ " | " ++ description.first_line() ++ "\n"'Empty output = no conflicts; go to Procedure B.
jj resolve --listonly works on the working-copy commit — on other revs it errorsNo conflicts found at this revision; use the revset.Resolve oldest-first, re-scanning between revisions (resolving a parent re-materializes its children's conflicts — never trust the earlier list).
jj edit <change-id>— checks the rev out.Read the conflict. jj's own marker dialect, NOT git's:
<<<<<<< conflict 1 of 1 %%%%%%% diff from: <base> (parents of rebased revision) \\\\\\\\\ to: <dest> (rebase destination) -old line +dest line +++++++ <rev> (rebased revision) your line >>>>>>> conflict 1 of 1 endsSide map:
%%%%%%% diff from:block = the destination side (moved trunk);+++++++ <rev>block = the rebased revision (your change). Outside the markers = shared context.Hand-merge (keep context, combine both intents, delete every marker line) or whole-side pick:
jj resolve --tool :ours <file> # destination side (moved trunk) jj resolve --tool :theirs <file> # your rebased changeSaving the file auto-snapshots into the checked-out rev; the
(conflict)tag disappears.jj squashonly if you resolved in a child rev.Re-scan
conflicts()→ next rev.
Restore
@.jj editmoved the working copy —jj edit <original @ change-id>before anything else snapshots into a stack rev.
Procedure B — divergent changes
Locate.
jj log -r 'divergent()' --no-graph \ -T 'change_id.short() ++ " | " ++ description.first_line() ++ "\n"'A divergent change = one change ID on 2+ commits (
id/0,id/1), shown tagged(divergent)injj log. Bookmark display turnsname??andjj bookmark listshows(conflicted)with@origin (ahead by N, behind by N).Pick the surviving twin. Default = YOUR local rewrite (the remote twin is usually a stale push or another agent's duplicate). Diff them first:
jj diff --from <id>/0 --to <id>/1Converge — abandon the losing twin, then repoint the bookmark. Both steps are required; abandoning alone leaves the bookmark conflicted and push rejected:
jj abandon <id>/0 # losing twin jj bookmark set <branch> -r <id>/1 # winner — offset form REQUIRED jj git push --remote origin -b <branch> # reads [move sideways ...]Bare
<id>(no/N) errorsChange ID ... is divergenton a divergent change — always address twins by offset.Verify.
jj log -r 'divergent()' --no-graph # empty jj bookmark list # no (conflicted), no name??
Push gate (both procedures)
jj log -r 'conflicts()' --no-graph # empty
jj log -r 'divergent()' --no-graph # empty
jj git push --remote origin -b <branch>
On this host push with --config signing.behavior=drop --config git.sign-on-push=false; GitHub squash-merge re-signs server-side.
Pitfalls
- Resurrection by reference. After abandoning a twin, ANY command that
references it (
jj new <a> <b>merge of both twins,jj show <id>/0) makes it reachable again and re-diverges the change. Abandon, then never name it again. - Offset is mandatory.
jj bookmark set <name> -r <id>on a divergent change fails; use<id>/1. This also bitesjj describe -r <id>. - Hidden ≠ gone.
jj abandonhides the twin;jj bookmark listmay keep showing it(hidden)under the conflicted entry until the bookmark is repointed and pushed. jj resolve --liston non-head revs errors — theconflicts()revset is the locator.- Marker dialect. Parsing git-style
<<<<<<<only mangles the file; the real sides live under%%%%%%%/+++++++. - Push rejection is the gate.
Won't push ... conflicts/Bookmark <name> is conflictedmean converge more; never bypass.
Verification
jj log -r 'conflicts()' --no-graph # empty
jj log -r 'divergent()' --no-graph # empty
jj bookmark list # unconflicted
jj status # @ restored
See also
sks-restack— the rebase step that precedes this skill.sks-dev-workflow— landing gates; never force-push stack branches.sks-stack— isolation when the main checkout is crowded.sks-gc— reclaim empty revs and stale workspaces afterwards.