Commit 49dfab9
committed
feat: add Jujutsu (jj) compatibility via VCSOperations abstraction
Adds end-to-end Jujutsu support for stacked PRs in colocated git+jj repos,
preserving jj change IDs across all spr operations and using jj-native
primitives (auto-rebase, conflicts as commits, non-blocking edits) instead
of mimicking git's rebase-session model.
Core architecture:
- New vcs.VCSOperations interface — the abstraction boundary for
history-rewriting operations (FetchAndRebase, GetLocalCommitStack,
AmendInto, EditStart/Finish/Abort, Fetch, PushBranches, etc.).
- vcs/git_ops.go: git implementation (extracted from spr.go, behavior-
preserving). Improved EditFinish uses .git/REBASE_HEAD to distinguish
conflict-resolution from initial edit stop and stages only tracked
changes (subsumes upstream's 606df43 and 0767a45 fixes).
- vcs/jj_ops.go: jj implementation. Uses jj-native commands:
jj git fetch + jj rebase, jj describe (for auto-added commit-id
trailers), jj squash --into, jj edit, jj bookmark set + jj git push.
- Commit.ChangeID field (empty in git mode, populated in jj mode).
- vcs/detect.go: factory chooses JjOps when .jj/ exists and User.NoJJ
is false; respects --no-jj flag and SPR_NOJJ env var.
Auto-revset (the key correctness fix):
- JjOps.GetLocalCommitStack queries trunk()..(@:: | ::@) — the
connected component containing @, excluding trunk — rather than the
position-dependent trunk()..@. Result: spr finds the full stack
regardless of where @ is, so 'spr update' from a mid-stack @ no
longer silently truncates the stack and closes PRs for the missing
commits.
- JjOps.CheckStackCompleteness detects multi-head ambiguity via
heads(trunk()..(@:: | ::@)) and refuses non-linear stacks with a
clear error pointing at jj rebase / jj edit as the fix.
- checkStackUsable (renamed from confirmIfIncompleteStack, used as
guard in update/merge/status/check/amend) blocks rather than prompts
on ambiguity — no safe 'continue anyway' answer exists.
jj-mode behavioral divergences:
- spr edit (jj): announces the action then runs 'jj edit <change-id>'.
No state file, no op-id snapshot, no rebase-session machinery —
jj is non-blocking and 'jj undo' handles revert. --done and --abort
are echo-only deprecations that point users at native jj.
- spr sync (jj): runs 'jj git fetch' (the literal 'git cherry-pick'
was broken in jj). No rebase promise — that's spr update's job.
VCS-layer integrations:
- Configurable branch prefix (upstream feature b01881d): jj_ops
threads cfg.User.BranchPrefix through PushBranches' bookmark glob.
- Stdout/stderr separation in vcs/jj_cmd.go: replaced
cmd.CombinedOutput() with separate capture so jj's stderr messages
(e.g. 'Rebased 1 descendant commits onto updated working copy')
don't corrupt parsed log output. Caught by the conflicted-push
integration test.
Testing:
- Two suites: unit (mockjj-based, fast) and integration (real jj via
the new vcs/jjtest package + a colocated test repo).
- Integration build tag //go:build integration; SPR_SKIP_JJ_INTEGRATION=1
env var to skip cleanly.
- jjtest.NewRepo creates a colocated git+jj repo in t.TempDir() with a
bare-repo origin; helpers for stack topology (AddCommit, Edit, Fork,
InsertAbove, SnapshotOpLog, AssertOpsSince).
- Headline regression: TestSprIntegration_StatusFromMidStack_ShowsFullStack
pins that GetLocalCommitStack from mid-stack @ returns the whole stack.
- TestJjIntegration_PushBranches_ConflictedCommit_Refused pins jj's
natural refusal to push conflicts (this is the test that revealed
the stderr bug).
- 100% statement coverage on jj-related production code (jj_ops.go,
jj_cmd.go, jj_parse.go).
- Op-log assertions verify that spr commands do exactly the expected
jj operations and no destructive ones.
Coverage tooling:
- scripts/coverage-merge/main.go merges two coverprofile files (max
hits per block, within and across profiles) and reports per-file
attribution (unit %, integ %, merged %, plus 'only in X' sections).
- Makefile targets: test-unit, test-integration, test-all, coverage-html.
- CI installs pinned JJ_VERSION (0.40.0), runs both suites with
-coverpkg=./..., merges, uploads as artifact.
Documentation:
- readme.md: new Jujutsu (jj) Support section covering setup, the
jj spr command set, divergences for edit/sync, the multi-head error
message, and the auto-revset behavior.
- docs/jj-mode-design.md: full design rationale including alternatives
considered (spr-tip bookmark, 3-option prompt), the stderr bug fix,
testing strategy, and what's deferred.
Files: +4500/-110 across 35 files.1 parent 0767a45 commit 49dfab9
29 files changed
Lines changed: 4400 additions & 121 deletions
File tree
- .github/workflows
- cmd
- amend
- spr
- config
- docs
- git
- mockgit
- scripts/coverage-merge
- spr
- vcs
- jjtest
- mockjj
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
5 | 10 | | |
6 | 11 | | |
7 | 12 | | |
| |||
15 | 20 | | |
16 | 21 | | |
17 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
18 | 31 | | |
19 | 32 | | |
20 | 33 | | |
21 | 34 | | |
22 | | - | |
| 35 | + | |
23 | 36 | | |
24 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
25 | 61 | | |
26 | 62 | | |
27 | 63 | | |
28 | 64 | | |
29 | 65 | | |
30 | 66 | | |
31 | 67 | | |
32 | | - | |
| 68 | + | |
33 | 69 | | |
34 | 70 | | |
35 | | - | |
36 | | - | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
65 | | - | |
| 66 | + | |
| 67 | + | |
66 | 68 | | |
67 | 69 | | |
68 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
95 | 97 | | |
96 | 98 | | |
97 | 99 | | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
98 | 111 | | |
99 | 112 | | |
100 | | - | |
| 113 | + | |
| 114 | + | |
101 | 115 | | |
102 | 116 | | |
103 | 117 | | |
| |||
160 | 174 | | |
161 | 175 | | |
162 | 176 | | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
163 | 183 | | |
164 | 184 | | |
165 | 185 | | |
| |||
346 | 366 | | |
347 | 367 | | |
348 | 368 | | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
349 | 388 | | |
350 | 389 | | |
351 | 390 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
55 | 56 | | |
56 | 57 | | |
57 | 58 | | |
| 59 | + | |
58 | 60 | | |
59 | 61 | | |
60 | 62 | | |
| |||
0 commit comments