@cvr/stacked 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -17
- package/bin/stacked +0 -0
- package/package.json +5 -1
- package/skills/stacked/SKILL.md +38 -30
- package/src/commands/clean.ts +85 -0
- package/src/commands/index.ts +4 -2
- package/src/commands/list.ts +43 -17
- package/src/commands/stacks.ts +33 -0
- package/src/commands/sync.ts +16 -4
- package/src/commands/restack.ts +0 -40
package/README.md
CHANGED
|
@@ -25,17 +25,23 @@ stacked create feat-auth-ui
|
|
|
25
25
|
# See the stack
|
|
26
26
|
stacked list
|
|
27
27
|
|
|
28
|
+
# See a specific stack by name
|
|
29
|
+
stacked list feat-auth
|
|
30
|
+
|
|
31
|
+
# List all stacks in the repo
|
|
32
|
+
stacked stacks
|
|
33
|
+
|
|
28
34
|
# Navigate
|
|
29
35
|
stacked top
|
|
30
36
|
stacked bottom
|
|
31
37
|
stacked checkout feat-auth
|
|
32
38
|
|
|
33
|
-
# After editing mid-stack, rebase children
|
|
34
|
-
stacked restack
|
|
35
|
-
|
|
36
39
|
# Sync entire stack with latest trunk
|
|
37
40
|
stacked sync
|
|
38
41
|
|
|
42
|
+
# After editing mid-stack, rebase only children
|
|
43
|
+
stacked sync --from feat-auth
|
|
44
|
+
|
|
39
45
|
# Push all branches + create/update PRs
|
|
40
46
|
stacked submit
|
|
41
47
|
stacked submit --draft
|
|
@@ -47,26 +53,31 @@ stacked adopt existing-branch --after feat-auth
|
|
|
47
53
|
# View commits per branch
|
|
48
54
|
stacked log
|
|
49
55
|
|
|
56
|
+
# Remove merged branches from stacks
|
|
57
|
+
stacked clean
|
|
58
|
+
stacked clean --dry-run
|
|
59
|
+
|
|
50
60
|
# Remove a branch from the stack
|
|
51
61
|
stacked delete feat-auth-ui
|
|
52
62
|
```
|
|
53
63
|
|
|
54
64
|
## Commands
|
|
55
65
|
|
|
56
|
-
| Command | Description
|
|
57
|
-
| ----------------- |
|
|
58
|
-
| `trunk [name]` | Get/set trunk branch
|
|
59
|
-
| `create <name>` | Create branch on top of current
|
|
60
|
-
| `list`
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
66
|
+
| Command | Description |
|
|
67
|
+
| ----------------- | ------------------------------------------------------------- |
|
|
68
|
+
| `trunk [name]` | Get/set trunk branch |
|
|
69
|
+
| `create <name>` | Create branch on top of current |
|
|
70
|
+
| `list [stack]` | Show stack branches (defaults to current stack) |
|
|
71
|
+
| `stacks` | List all stacks in the repo |
|
|
72
|
+
| `checkout <name>` | Switch to branch |
|
|
73
|
+
| `top` | Jump to top of stack |
|
|
74
|
+
| `bottom` | Jump to bottom of stack |
|
|
75
|
+
| `sync` | Fetch + rebase stack on trunk (--from to start from a branch) |
|
|
76
|
+
| `clean` | Remove merged branches from stacks (--dry-run to preview) |
|
|
77
|
+
| `delete <name>` | Remove branch from stack + git |
|
|
78
|
+
| `submit` | Push all + create/update PRs via `gh` |
|
|
79
|
+
| `adopt <branch>` | Add existing branch to stack |
|
|
80
|
+
| `log` | Show commits grouped by branch |
|
|
70
81
|
|
|
71
82
|
## Data Model
|
|
72
83
|
|
package/bin/stacked
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/skills/stacked/SKILL.md
CHANGED
|
@@ -26,20 +26,21 @@ What do you need?
|
|
|
26
26
|
|
|
27
27
|
## Quick Reference
|
|
28
28
|
|
|
29
|
-
| Command | What it does
|
|
30
|
-
| ------------------------- |
|
|
31
|
-
| `stacked trunk [name]` | Get/set trunk branch (default: main)
|
|
32
|
-
| `stacked create <name>` | Create branch on top of current branch
|
|
33
|
-
| `stacked list`
|
|
34
|
-
| `stacked
|
|
35
|
-
| `stacked
|
|
36
|
-
| `stacked
|
|
37
|
-
| `stacked
|
|
38
|
-
| `stacked
|
|
39
|
-
| `stacked
|
|
40
|
-
| `stacked
|
|
41
|
-
| `stacked
|
|
42
|
-
| `stacked
|
|
29
|
+
| Command | What it does |
|
|
30
|
+
| ------------------------- | ------------------------------------------------------------- |
|
|
31
|
+
| `stacked trunk [name]` | Get/set trunk branch (default: main) |
|
|
32
|
+
| `stacked create <name>` | Create branch on top of current branch |
|
|
33
|
+
| `stacked list [stack]` | Show stack branches (defaults to current stack) |
|
|
34
|
+
| `stacked stacks` | List all stacks in the repo |
|
|
35
|
+
| `stacked checkout <name>` | Switch to branch in stack |
|
|
36
|
+
| `stacked top` | Jump to top of stack |
|
|
37
|
+
| `stacked bottom` | Jump to bottom of stack |
|
|
38
|
+
| `stacked sync` | Fetch + rebase stack on trunk (--from to start from a branch) |
|
|
39
|
+
| `stacked clean` | Remove merged branches from stacks (--dry-run to preview) |
|
|
40
|
+
| `stacked delete <name>` | Remove branch from stack + delete git branch |
|
|
41
|
+
| `stacked submit` | Push all branches + create/update PRs via `gh` |
|
|
42
|
+
| `stacked adopt <branch>` | Add existing git branch into the stack |
|
|
43
|
+
| `stacked log` | Show commits grouped by branch |
|
|
43
44
|
|
|
44
45
|
## Setup
|
|
45
46
|
|
|
@@ -74,8 +75,10 @@ stacked create hotfix --from feat-auth
|
|
|
74
75
|
## Viewing the Stack
|
|
75
76
|
|
|
76
77
|
```sh
|
|
77
|
-
stacked list
|
|
78
|
-
stacked
|
|
78
|
+
stacked list # shows current stack's branches
|
|
79
|
+
stacked list feat-auth # shows a specific stack by name
|
|
80
|
+
stacked stacks # lists all stacks in the repo
|
|
81
|
+
stacked log # shows commits grouped by branch
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
## Navigation
|
|
@@ -86,28 +89,22 @@ stacked top # jump to top of stack
|
|
|
86
89
|
stacked bottom # jump to bottom (trunk-adjacent)
|
|
87
90
|
```
|
|
88
91
|
|
|
89
|
-
## Rebasing
|
|
92
|
+
## Syncing / Rebasing
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Edit a branch mid-stack, then rebase everything above it:
|
|
94
|
+
Fetch latest trunk and rebase the entire stack bottom-to-top:
|
|
94
95
|
|
|
95
96
|
```sh
|
|
96
|
-
stacked
|
|
97
|
-
# ... make changes, commit ...
|
|
98
|
-
stacked restack # rebases feat-auth-ui and feat-auth-tests
|
|
97
|
+
stacked sync
|
|
99
98
|
```
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Pull latest trunk and rebase the entire stack bottom-to-top:
|
|
100
|
+
After mid-stack changes, rebase only the branches above a specific point:
|
|
104
101
|
|
|
105
102
|
```sh
|
|
106
|
-
stacked
|
|
103
|
+
stacked checkout feat-auth
|
|
104
|
+
# ... make changes, commit ...
|
|
105
|
+
stacked sync --from feat-auth # rebases only children of feat-auth
|
|
107
106
|
```
|
|
108
107
|
|
|
109
|
-
This fetches, then rebases each branch onto its parent starting from the bottom.
|
|
110
|
-
|
|
111
108
|
## Submitting
|
|
112
109
|
|
|
113
110
|
Push all stack branches and create/update GitHub PRs with correct base branches:
|
|
@@ -130,6 +127,17 @@ stacked adopt existing-branch # append to top
|
|
|
130
127
|
stacked adopt existing-branch --after feat-auth # insert after specific branch
|
|
131
128
|
```
|
|
132
129
|
|
|
130
|
+
## Cleaning Up Merged Branches
|
|
131
|
+
|
|
132
|
+
After PRs are merged on GitHub, clean up the local branches and stack metadata:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
stacked clean # removes all merged branches from all stacks
|
|
136
|
+
stacked clean --dry-run # preview what would be removed
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`list` also shows merge status per branch (`[merged]`, `[closed]`, `[#N]` for open PRs).
|
|
140
|
+
|
|
133
141
|
## Deleting
|
|
134
142
|
|
|
135
143
|
```sh
|
|
@@ -160,7 +168,7 @@ stacked create feat-auth-ui
|
|
|
160
168
|
# 3. Need to fix something mid-stack
|
|
161
169
|
stacked checkout feat-auth
|
|
162
170
|
# ... fix, commit ...
|
|
163
|
-
stacked
|
|
171
|
+
stacked sync --from feat-auth # rebase children
|
|
164
172
|
|
|
165
173
|
# 4. Sync with latest main
|
|
166
174
|
stacked sync
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Command, Flag } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect } from "effect";
|
|
3
|
+
import { GitService } from "../services/Git.js";
|
|
4
|
+
import { GitHubService } from "../services/GitHub.js";
|
|
5
|
+
import { StackService } from "../services/Stack.js";
|
|
6
|
+
|
|
7
|
+
const dryRunFlag = Flag.boolean("dry-run");
|
|
8
|
+
|
|
9
|
+
export const clean = Command.make("clean", { dryRun: dryRunFlag }).pipe(
|
|
10
|
+
Command.withDescription("Remove merged branches from stacks (bottom-up)"),
|
|
11
|
+
Command.withHandler(({ dryRun }) =>
|
|
12
|
+
Effect.gen(function* () {
|
|
13
|
+
const git = yield* GitService;
|
|
14
|
+
const gh = yield* GitHubService;
|
|
15
|
+
const stacks = yield* StackService;
|
|
16
|
+
|
|
17
|
+
const currentBranch = yield* git.currentBranch();
|
|
18
|
+
const data = yield* stacks.load();
|
|
19
|
+
|
|
20
|
+
const toRemove: Array<{ stackName: string; branch: string }> = [];
|
|
21
|
+
const skippedMerged: Array<{ stackName: string; branch: string }> = [];
|
|
22
|
+
|
|
23
|
+
for (const [stackName, stack] of Object.entries(data.stacks)) {
|
|
24
|
+
let hitNonMerged = false;
|
|
25
|
+
for (const branch of stack.branches) {
|
|
26
|
+
const pr = yield* gh.getPR(branch).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
27
|
+
const isMerged = pr !== null && pr.state === "MERGED";
|
|
28
|
+
|
|
29
|
+
if (!hitNonMerged && isMerged) {
|
|
30
|
+
toRemove.push({ stackName, branch });
|
|
31
|
+
} else {
|
|
32
|
+
if (!isMerged) hitNonMerged = true;
|
|
33
|
+
if (isMerged) skippedMerged.push({ stackName, branch });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (toRemove.length === 0) {
|
|
39
|
+
yield* Console.log("Nothing to clean");
|
|
40
|
+
if (skippedMerged.length > 0) {
|
|
41
|
+
yield* Console.log(
|
|
42
|
+
`\nNote: ${skippedMerged.length} merged branch${skippedMerged.length === 1 ? "" : "es"} skipped (non-merged branches below):`,
|
|
43
|
+
);
|
|
44
|
+
for (const { branch, stackName } of skippedMerged) {
|
|
45
|
+
yield* Console.log(` ${branch} (${stackName})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const { stackName, branch } of toRemove) {
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
yield* Console.log(`Would remove ${branch} from ${stackName}`);
|
|
54
|
+
} else {
|
|
55
|
+
if (currentBranch === branch) {
|
|
56
|
+
const trunk = yield* stacks.getTrunk();
|
|
57
|
+
yield* git.checkout(trunk);
|
|
58
|
+
}
|
|
59
|
+
yield* stacks.removeBranch(stackName, branch);
|
|
60
|
+
yield* git.deleteBranch(branch, true).pipe(Effect.catch(() => Effect.void));
|
|
61
|
+
yield* Console.log(`Removed ${branch} from ${stackName}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (dryRun) {
|
|
66
|
+
yield* Console.log(
|
|
67
|
+
`\n${toRemove.length} branch${toRemove.length === 1 ? "" : "es"} would be removed`,
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
yield* Console.log(
|
|
71
|
+
`\nCleaned ${toRemove.length} merged branch${toRemove.length === 1 ? "" : "es"}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (skippedMerged.length > 0) {
|
|
76
|
+
yield* Console.log(
|
|
77
|
+
`\nNote: ${skippedMerged.length} merged branch${skippedMerged.length === 1 ? "" : "es"} skipped (non-merged branches below):`,
|
|
78
|
+
);
|
|
79
|
+
for (const { branch, stackName } of skippedMerged) {
|
|
80
|
+
yield* Console.log(` ${branch} (${stackName})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
),
|
|
85
|
+
);
|
package/src/commands/index.ts
CHANGED
|
@@ -2,15 +2,16 @@ import { Command } from "effect/unstable/cli";
|
|
|
2
2
|
import { trunk } from "./trunk.js";
|
|
3
3
|
import { create } from "./create.js";
|
|
4
4
|
import { list } from "./list.js";
|
|
5
|
+
import { stacks } from "./stacks.js";
|
|
5
6
|
import { checkout } from "./checkout.js";
|
|
6
7
|
import { top } from "./top.js";
|
|
7
8
|
import { bottom } from "./bottom.js";
|
|
8
9
|
import { sync } from "./sync.js";
|
|
9
|
-
import { restack } from "./restack.js";
|
|
10
10
|
import { deleteCmd } from "./delete.js";
|
|
11
11
|
import { submit } from "./submit.js";
|
|
12
12
|
import { adopt } from "./adopt.js";
|
|
13
13
|
import { log } from "./log.js";
|
|
14
|
+
import { clean } from "./clean.js";
|
|
14
15
|
|
|
15
16
|
const root = Command.make("stacked").pipe(
|
|
16
17
|
Command.withDescription("Branch-based stacked PR manager"),
|
|
@@ -21,14 +22,15 @@ export const command = root.pipe(
|
|
|
21
22
|
trunk,
|
|
22
23
|
create,
|
|
23
24
|
list,
|
|
25
|
+
stacks,
|
|
24
26
|
checkout,
|
|
25
27
|
top,
|
|
26
28
|
bottom,
|
|
27
29
|
sync,
|
|
28
|
-
restack,
|
|
29
30
|
deleteCmd,
|
|
30
31
|
submit,
|
|
31
32
|
adopt,
|
|
32
33
|
log,
|
|
34
|
+
clean,
|
|
33
35
|
]),
|
|
34
36
|
);
|
package/src/commands/list.ts
CHANGED
|
@@ -1,47 +1,73 @@
|
|
|
1
|
-
import { Command } from "effect/unstable/cli";
|
|
2
|
-
import { Console, Effect } from "effect";
|
|
1
|
+
import { Argument, Command } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
3
|
import { GitService } from "../services/Git.js";
|
|
4
|
+
import { GitHubService } from "../services/GitHub.js";
|
|
4
5
|
import { StackService } from "../services/Stack.js";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const stackNameArg = Argument.string("stack").pipe(Argument.optional);
|
|
8
|
+
|
|
9
|
+
export const list = Command.make("list", { stackName: stackNameArg }).pipe(
|
|
10
|
+
Command.withDescription("Show stack branches (defaults to current stack)"),
|
|
11
|
+
Command.withHandler(({ stackName }) =>
|
|
9
12
|
Effect.gen(function* () {
|
|
10
13
|
const git = yield* GitService;
|
|
14
|
+
const gh = yield* GitHubService;
|
|
11
15
|
const stacks = yield* StackService;
|
|
12
16
|
|
|
13
17
|
const currentBranch = yield* git.currentBranch();
|
|
14
18
|
const data = yield* stacks.load();
|
|
15
19
|
const trunk = data.trunk;
|
|
16
20
|
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
let targetStackName: string | null = null;
|
|
22
|
+
let targetStack: { readonly branches: readonly string[] } | null = null;
|
|
23
|
+
|
|
24
|
+
if (Option.isSome(stackName)) {
|
|
25
|
+
const s = data.stacks[stackName.value];
|
|
26
|
+
if (s === undefined) {
|
|
27
|
+
yield* Console.error(`Stack "${stackName.value}" not found`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
targetStackName = stackName.value;
|
|
31
|
+
targetStack = s;
|
|
32
|
+
} else {
|
|
33
|
+
for (const [name, stack] of Object.entries(data.stacks)) {
|
|
34
|
+
if (stack.branches.includes(currentBranch)) {
|
|
35
|
+
targetStackName = name;
|
|
36
|
+
targetStack = stack;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
22
39
|
}
|
|
23
40
|
}
|
|
24
41
|
|
|
25
|
-
if (
|
|
42
|
+
if (targetStackName === null || targetStack === null) {
|
|
26
43
|
yield* Console.log("Not on a stacked branch");
|
|
27
44
|
return;
|
|
28
45
|
}
|
|
29
46
|
|
|
30
|
-
const stack = data.stacks[currentStackName];
|
|
31
|
-
if (stack === undefined) return;
|
|
32
47
|
const lines: string[] = [];
|
|
33
48
|
|
|
34
|
-
lines.push(`Stack: ${
|
|
49
|
+
lines.push(`Stack: ${targetStackName}`);
|
|
35
50
|
lines.push(`Trunk: ${trunk}`);
|
|
36
51
|
lines.push("");
|
|
37
52
|
|
|
38
|
-
for (let i =
|
|
39
|
-
const branch =
|
|
53
|
+
for (let i = targetStack.branches.length - 1; i >= 0; i--) {
|
|
54
|
+
const branch = targetStack.branches[i];
|
|
40
55
|
if (branch === undefined) continue;
|
|
41
56
|
const isCurrent = branch === currentBranch;
|
|
42
57
|
const marker = isCurrent ? "* " : " ";
|
|
43
58
|
const prefix = i === 0 ? "└─" : "├─";
|
|
44
|
-
|
|
59
|
+
|
|
60
|
+
const pr = yield* gh.getPR(branch).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
61
|
+
const status =
|
|
62
|
+
pr === null
|
|
63
|
+
? ""
|
|
64
|
+
: pr.state === "MERGED"
|
|
65
|
+
? " [merged]"
|
|
66
|
+
: pr.state === "CLOSED"
|
|
67
|
+
? " [closed]"
|
|
68
|
+
: ` [#${pr.number}]`;
|
|
69
|
+
|
|
70
|
+
lines.push(`${marker}${prefix} ${branch}${status}`);
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
yield* Console.log(lines.join("\n"));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect } from "effect";
|
|
3
|
+
import { GitService } from "../services/Git.js";
|
|
4
|
+
import { StackService } from "../services/Stack.js";
|
|
5
|
+
|
|
6
|
+
export const stacks = Command.make("stacks").pipe(
|
|
7
|
+
Command.withDescription("List all stacks in the repo"),
|
|
8
|
+
Command.withHandler(() =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const git = yield* GitService;
|
|
11
|
+
const stackService = yield* StackService;
|
|
12
|
+
|
|
13
|
+
const currentBranch = yield* git.currentBranch();
|
|
14
|
+
const data = yield* stackService.load();
|
|
15
|
+
|
|
16
|
+
const entries = Object.entries(data.stacks);
|
|
17
|
+
if (entries.length === 0) {
|
|
18
|
+
yield* Console.log("No stacks");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const lines: string[] = [];
|
|
23
|
+
for (const [name, stack] of Object.entries(data.stacks)) {
|
|
24
|
+
const isCurrent = stack.branches.includes(currentBranch);
|
|
25
|
+
const marker = isCurrent ? "* " : " ";
|
|
26
|
+
const count = stack.branches.length;
|
|
27
|
+
lines.push(`${marker}${name} (${count} branch${count === 1 ? "" : "es"})`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
yield* Console.log(lines.join("\n"));
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
);
|
package/src/commands/sync.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { GitService } from "../services/Git.js";
|
|
|
4
4
|
import { StackService } from "../services/Stack.js";
|
|
5
5
|
|
|
6
6
|
const trunkFlag = Flag.string("trunk").pipe(Flag.optional, Flag.withAlias("t"));
|
|
7
|
+
const fromFlag = Flag.string("from").pipe(Flag.optional, Flag.withAlias("f"));
|
|
7
8
|
|
|
8
|
-
export const sync = Command.make("sync", { trunk: trunkFlag }).pipe(
|
|
9
|
-
Command.withDescription("
|
|
10
|
-
Command.withHandler(({ trunk: trunkOpt }) =>
|
|
9
|
+
export const sync = Command.make("sync", { trunk: trunkFlag, from: fromFlag }).pipe(
|
|
10
|
+
Command.withDescription("Fetch and rebase stack on trunk. Use --from to start from a branch."),
|
|
11
|
+
Command.withHandler(({ trunk: trunkOpt, from: fromOpt }) =>
|
|
11
12
|
Effect.gen(function* () {
|
|
12
13
|
const git = yield* GitService;
|
|
13
14
|
const stacks = yield* StackService;
|
|
@@ -25,8 +26,19 @@ export const sync = Command.make("sync", { trunk: trunkFlag }).pipe(
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const { branches } = result.stack;
|
|
29
|
+
const fromBranch = Option.isSome(fromOpt) ? fromOpt.value : undefined;
|
|
30
|
+
|
|
31
|
+
let startIdx = 0;
|
|
32
|
+
if (fromBranch !== undefined) {
|
|
33
|
+
const idx = branches.indexOf(fromBranch);
|
|
34
|
+
if (idx === -1) {
|
|
35
|
+
yield* Console.error(`Branch "${fromBranch}" not found in stack`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
startIdx = idx + 1;
|
|
39
|
+
}
|
|
28
40
|
|
|
29
|
-
for (let i =
|
|
41
|
+
for (let i = startIdx; i < branches.length; i++) {
|
|
30
42
|
const branch = branches[i];
|
|
31
43
|
if (branch === undefined) continue;
|
|
32
44
|
const base = i === 0 ? `origin/${trunk}` : (branches[i - 1] ?? `origin/${trunk}`);
|
package/src/commands/restack.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Command } from "effect/unstable/cli";
|
|
2
|
-
import { Console, Effect } from "effect";
|
|
3
|
-
import { GitService } from "../services/Git.js";
|
|
4
|
-
import { StackService } from "../services/Stack.js";
|
|
5
|
-
|
|
6
|
-
export const restack = Command.make("restack").pipe(
|
|
7
|
-
Command.withDescription("Rebase children after mid-stack changes"),
|
|
8
|
-
Command.withHandler(() =>
|
|
9
|
-
Effect.gen(function* () {
|
|
10
|
-
const git = yield* GitService;
|
|
11
|
-
const stacks = yield* StackService;
|
|
12
|
-
|
|
13
|
-
const currentBranch = yield* git.currentBranch();
|
|
14
|
-
const result = yield* stacks.currentStack();
|
|
15
|
-
if (result === null) {
|
|
16
|
-
yield* Console.error("Not on a stacked branch");
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const { branches } = result.stack;
|
|
21
|
-
const idx = branches.indexOf(currentBranch);
|
|
22
|
-
if (idx === -1) {
|
|
23
|
-
yield* Console.error("Current branch not found in stack");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
for (let i = idx + 1; i < branches.length; i++) {
|
|
28
|
-
const branch = branches[i];
|
|
29
|
-
if (branch === undefined) continue;
|
|
30
|
-
const base = branches[i - 1] ?? currentBranch;
|
|
31
|
-
yield* Console.log(`Rebasing ${branch} onto ${base}...`);
|
|
32
|
-
yield* git.checkout(branch);
|
|
33
|
-
yield* git.rebase(base);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
yield* git.checkout(currentBranch);
|
|
37
|
-
yield* Console.log("Stack restacked");
|
|
38
|
-
}),
|
|
39
|
-
),
|
|
40
|
-
);
|