You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(extract): enforce merge-point floor and remove --after-merge
Remove the --after-merge flag from arb extract. Users now specify split
points explicitly with --starting-with, which is clearer about what gets
extracted. The first post-merge commit SHA is visible in arb status -v
and arb log.
Add merge-point floor validation: in repos where the branch has been
merged into its base, split points below the merge point are rejected
with a clear error. This closes an asymmetry where extract allowed
grabbing pre-merge commits that the integrate/rebase path would never
replay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Extract: enforce merge-point floor and remove --after-merge
2
+
3
+
Date: 2026-03-28
4
+
5
+
## Context
6
+
7
+
`arb extract` splits a branch at a boundary commit, creating a new stacked workspace. The command validates that split points are above the merge-base (the point where the branch diverged from its base). However, when a branch has been merged into its base (e.g., via squash-merge on the remote) and the user continued committing, the pre-merge commits are already represented on the base. The merge-base floor check does not catch this — it only checks against the diverge point, not the merge point.
8
+
9
+
Meanwhile, the integrate/rebase path (`classify-integrate.ts`) correctly limits replay to only `newCommitsAfter` commits, effectively using the merge point as its floor. This created an asymmetry: extract allowed grabbing commits that rebase would never replay.
10
+
11
+
Separately, `--after-merge` auto-detected the merge boundary and extracted post-merge commits. Since the merge point is the natural lower bound for valid extraction, `--after-merge` was equivalent to "start at the lowest valid split point" — a convenience, but not a primitive. Users can look up the first post-merge commit from `arb status -v` or `arb log` and use `--starting-with` explicitly, which is clearer about what gets extracted.
12
+
13
+
## Options
14
+
15
+
### Tighten bounds only (keep --after-merge)
16
+
Fix the validation gap by rejecting split points below the merge point. Keep `--after-merge` as a convenience for auto-detecting the boundary.
17
+
-**Pros:** Fixes the bug. Non-breaking.
18
+
-**Cons:**`--after-merge` is now just "use the lowest valid split point" — a thin convenience over `--starting-with`.
19
+
20
+
### Tighten bounds and remove --after-merge
21
+
Fix the validation and remove the `--after-merge` flag. Users specify split points explicitly.
22
+
-**Pros:** Simpler command surface. Extract requires users to be explicit about what they're splitting. No implicit-and-possible-to-misinterpret behavior.
23
+
-**Cons:** Slightly more steps for the post-merge extraction workflow.
24
+
25
+
## Decision
26
+
27
+
Tighten the merge-point floor validation and remove `--after-merge`. The flag was a convenience for a specific scenario, and the explicit `--starting-with` workflow is more transparent. Pre-release status makes this a clean time to simplify the command surface.
28
+
29
+
## Reasoning
30
+
31
+
The whole point of extract is to choose where to split a branch — making the user specify the split point explicitly is consistent with this intent. `--after-merge` obscured which commits were being moved. With tighter bound validation, invalid split points produce clear errors explaining why, guiding users toward valid choices.
32
+
33
+
The integrate/rebase path already enforced the merge-point floor implicitly by limiting replay. Making extract consistent with this boundary closes the asymmetry.
34
+
35
+
## Consequences
36
+
37
+
- Users who relied on `--after-merge` must now identify the first post-merge commit SHA manually. `arb status -v` and `arb log` both surface this information.
38
+
- The `arb push` hint for merged-new-work repos now suggests `arb extract <workspace> --starting-with <sha>` instead of `--after-merge`.
39
+
- Shell completions and help topics are updated to remove the flag.
40
+
- New `below-merge-point` skip flag blocks extraction when a split point falls before the merge point.
@@ -40,19 +40,14 @@ export function registerExtractCommand(program: Command): void {
40
40
program
41
41
.command("extract <workspace>")
42
42
.addOption(
43
-
newOption("--ending-with <specs...>","Extract prefix (base through boundary) into new workspace")
44
-
.conflicts("startingWith")
45
-
.conflicts("afterMerge"),
43
+
newOption("--ending-with <specs...>","Extract prefix (base through boundary) into new workspace").conflicts(
44
+
"startingWith",
45
+
),
46
46
)
47
47
.addOption(
48
-
newOption("--starting-with <specs...>","Extract suffix (boundary through tip) into new workspace")
49
-
.conflicts("endingWith")
50
-
.conflicts("afterMerge"),
51
-
)
52
-
.addOption(
53
-
newOption("--after-merge","Extract suffix after merge point (auto-detect)")
54
-
.conflicts("endingWith")
55
-
.conflicts("startingWith"),
48
+
newOption("--starting-with <specs...>","Extract suffix (boundary through tip) into new workspace").conflicts(
49
+
"endingWith",
50
+
),
56
51
)
57
52
.option("-b, --branch <name>","Branch name for new workspace (defaults to workspace name)")
58
53
.option("--fetch","Fetch from all remotes before extract (default)")
@@ -68,17 +63,17 @@ export function registerExtractCommand(program: Command): void {
68
63
)
69
64
.summary("Extract commits into a new workspace")
70
65
.description(
71
-
"Examples:\n\n arb extract prereq --ending-with abc123 Extract prefix into 'prereq'\n arb extract cont --starting-with abc123 Extract suffix into 'cont'\n arb extract cont --after-merge Extract post-merge commits\n arb extract prereq --ending-with abc123,def456 Multiple repos (auto-detect)\n arb extract prereq --ending-with api:HEAD~3 Per-repo with explicit prefix\n\nSplits the current workspace's branch at a boundary commit, creating a new stacked workspace.\n\nWith --ending-with, extracts the prefix (base through boundary, inclusive) into a new lower workspace. The original workspace is rebased to stack on top.\n\nWith --starting-with, extracts the suffix (boundary through tip, inclusive) into a new upper workspace. The original workspace is reset to before the boundary.\n\nWith --after-merge, auto-detects the merge point and extracts post-merge commits into a new workspace.\n\nSplit points are specified as commit SHAs (auto-detect repo), <repo>:<commit-ish> (explicit), or tags. Multiple values can be comma-separated.\n\nRepos without an explicit split point have zero commits extracted — they are included in both workspaces but just track the base.",
66
+
"Examples:\n\n arb extract prereq --ending-with abc123 Extract prefix into 'prereq'\n arb extract cont --starting-with abc123 Extract suffix into 'cont'\n arb extract prereq --ending-with abc123,def456 Multiple repos (auto-detect)\n arb extract prereq --ending-with api:HEAD~3 Per-repo with explicit prefix\n\nSplits the current workspace's branch at a boundary commit, creating a new stacked workspace.\n\nWith --ending-with, extracts the prefix (base through boundary, inclusive) into a new lower workspace. The original workspace is rebased to stack on top.\n\nWith --starting-with, extracts the suffix (boundary through tip, inclusive) into a new upper workspace. The original workspace is reset to before the boundary.\n\nSplit points are specified as commit SHAs (auto-detect repo), <repo>:<commit-ish> (explicit), or tags. Multiple values can be comma-separated.\n\nRepos without an explicit split point have zero commits extracted — they are included in both workspaces but just track the base.\n\nIn base-merged workspaces, split points must be at or after the merge point — pre-merge commits are already on the default branch.",
Copy file name to clipboardExpand all lines: src/commands/push.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -267,7 +267,7 @@ export function buildPushPlanNodes(
267
267
nodes.push({
268
268
kind: "hint",
269
269
cell: cell(
270
-
` hint: ${plural(mergedNewWorkCount,"repo")}${mergedNewWorkCount===1 ? "was" : "were"} merged but ${mergedNewWorkCount===1 ? "has" : "have"} new commits since — run 'arb rebase' or 'arb extract --after-merge <workspace>' to split`,
270
+
` hint: ${plural(mergedNewWorkCount,"repo")}${mergedNewWorkCount===1 ? "was" : "were"} merged but ${mergedNewWorkCount===1 ? "has" : "have"} new commits since — run 'arb rebase' or 'arb extract <workspace> --starting-with <sha>' to split`,
0 commit comments