dubstack 0.1.3
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/LICENSE +21 -0
- package/README.md +276 -0
- package/dist/commands/create.d.ts +18 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +35 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/init.d.ts +18 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +41 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/log.d.ts +12 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +77 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/restack.d.ts +33 -0
- package/dist/commands/restack.d.ts.map +1 -0
- package/dist/commands/restack.js +190 -0
- package/dist/commands/restack.js.map +1 -0
- package/dist/commands/undo.d.ts +21 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +63 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/errors.d.ts +15 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +18 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/git.d.ts +69 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +184 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/state.d.ts +70 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +110 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/undo-log.d.ts +33 -0
- package/dist/lib/undo-log.d.ts.map +1 -0
- package/dist/lib/undo-log.js +37 -0
- package/dist/lib/undo-log.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { DubError } from "../lib/errors.js";
|
|
4
|
+
import { branchExists, checkoutBranch, getBranchTip, getCurrentBranch, getMergeBase, rebaseContinue as gitRebaseContinue, isWorkingTreeClean, rebaseOnto, } from "../lib/git.js";
|
|
5
|
+
import { getDubDir, readState } from "../lib/state.js";
|
|
6
|
+
import { saveUndoEntry } from "../lib/undo-log.js";
|
|
7
|
+
/**
|
|
8
|
+
* Rebases all branches in the current stack onto their updated parents.
|
|
9
|
+
*
|
|
10
|
+
* Uses a snapshot-before-rebase strategy: captures every branch's tip
|
|
11
|
+
* BEFORE starting any rebases, then uses `git rebase --onto <parent_new_tip>
|
|
12
|
+
* <parent_old_tip> <child>`. This prevents the duplication bug where a child
|
|
13
|
+
* replays its parent's already-rebased commits.
|
|
14
|
+
*
|
|
15
|
+
* On conflict, writes progress to `restack-progress.json` so
|
|
16
|
+
* `dub restack --continue` can resume.
|
|
17
|
+
*
|
|
18
|
+
* @param cwd - Working directory
|
|
19
|
+
* @returns Result with status, list of rebased branches, and optional conflict branch
|
|
20
|
+
* @throws {DubError} If not initialized, dirty tree, not in a stack, or branch missing
|
|
21
|
+
*/
|
|
22
|
+
export async function restack(cwd) {
|
|
23
|
+
const state = await readState(cwd);
|
|
24
|
+
if (!(await isWorkingTreeClean(cwd))) {
|
|
25
|
+
throw new DubError("Working tree has uncommitted changes. Commit or stash them before restacking.");
|
|
26
|
+
}
|
|
27
|
+
const originalBranch = await getCurrentBranch(cwd);
|
|
28
|
+
const targetStacks = getTargetStacks(state.stacks, originalBranch);
|
|
29
|
+
if (targetStacks.length === 0) {
|
|
30
|
+
throw new DubError(`Branch '${originalBranch}' is not part of any stack. Run 'dub create' first.`);
|
|
31
|
+
}
|
|
32
|
+
const allBranches = targetStacks.flatMap((s) => s.branches);
|
|
33
|
+
for (const branch of allBranches) {
|
|
34
|
+
if (!(await branchExists(branch.name, cwd))) {
|
|
35
|
+
throw new DubError(`Branch '${branch.name}' is tracked in state but no longer exists in git.\n` +
|
|
36
|
+
" Remove it from the stack or recreate it before restacking.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Snapshot all branch tips BEFORE building steps or rebasing
|
|
40
|
+
const branchTips = {};
|
|
41
|
+
for (const branch of allBranches) {
|
|
42
|
+
branchTips[branch.name] = await getBranchTip(branch.name, cwd);
|
|
43
|
+
}
|
|
44
|
+
const steps = await buildRestackSteps(targetStacks, cwd);
|
|
45
|
+
if (steps.length === 0) {
|
|
46
|
+
return { status: "up-to-date", rebased: [] };
|
|
47
|
+
}
|
|
48
|
+
await saveUndoEntry({
|
|
49
|
+
operation: "restack",
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
previousBranch: originalBranch,
|
|
52
|
+
previousState: structuredClone(state),
|
|
53
|
+
branchTips,
|
|
54
|
+
createdBranches: [],
|
|
55
|
+
}, cwd);
|
|
56
|
+
const progress = { originalBranch, steps };
|
|
57
|
+
await writeProgress(progress, cwd);
|
|
58
|
+
return executeRestackSteps(progress, cwd);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Continues a restack after conflict resolution.
|
|
62
|
+
*
|
|
63
|
+
* Reads the saved progress file, finishes the in-progress rebase,
|
|
64
|
+
* then resumes with remaining branches.
|
|
65
|
+
*
|
|
66
|
+
* @param cwd - Working directory
|
|
67
|
+
* @throws {DubError} If no restack is in progress
|
|
68
|
+
*/
|
|
69
|
+
export async function restackContinue(cwd) {
|
|
70
|
+
const progress = await readProgress(cwd);
|
|
71
|
+
if (!progress) {
|
|
72
|
+
throw new DubError("No restack in progress. Run 'dub restack' to start.");
|
|
73
|
+
}
|
|
74
|
+
await gitRebaseContinue(cwd);
|
|
75
|
+
const conflictedStep = progress.steps.find((s) => s.status === "conflicted");
|
|
76
|
+
if (conflictedStep) {
|
|
77
|
+
conflictedStep.status = "done";
|
|
78
|
+
}
|
|
79
|
+
return executeRestackSteps(progress, cwd);
|
|
80
|
+
}
|
|
81
|
+
async function executeRestackSteps(progress, cwd) {
|
|
82
|
+
const rebased = [];
|
|
83
|
+
for (const step of progress.steps) {
|
|
84
|
+
if (step.status !== "pending") {
|
|
85
|
+
if (step.status === "done")
|
|
86
|
+
rebased.push(step.branch);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const parentNewTip = await getBranchTip(step.parent, cwd);
|
|
90
|
+
if (parentNewTip === step.parentOldTip) {
|
|
91
|
+
step.status = "skipped";
|
|
92
|
+
await writeProgress(progress, cwd);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await rebaseOnto(parentNewTip, step.parentOldTip, step.branch, cwd);
|
|
97
|
+
step.status = "done";
|
|
98
|
+
rebased.push(step.branch);
|
|
99
|
+
await writeProgress(progress, cwd);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof DubError && error.message.includes("Conflict")) {
|
|
103
|
+
step.status = "conflicted";
|
|
104
|
+
await writeProgress(progress, cwd);
|
|
105
|
+
return { status: "conflict", rebased, conflictBranch: step.branch };
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await clearProgress(cwd);
|
|
111
|
+
await checkoutBranch(progress.originalBranch, cwd);
|
|
112
|
+
const allSkipped = progress.steps.every((s) => s.status === "skipped" || s.status === "done");
|
|
113
|
+
return {
|
|
114
|
+
status: rebased.length === 0 && allSkipped ? "up-to-date" : "success",
|
|
115
|
+
rebased,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function getTargetStacks(stacks, currentBranch) {
|
|
119
|
+
// If current branch is a root of any stacks, restack all of them
|
|
120
|
+
const rootStacks = stacks.filter((s) => s.branches.some((b) => b.name === currentBranch && b.type === "root"));
|
|
121
|
+
if (rootStacks.length > 0)
|
|
122
|
+
return rootStacks;
|
|
123
|
+
// Otherwise, find the stack containing the current branch
|
|
124
|
+
const stack = stacks.find((s) => s.branches.some((b) => b.name === currentBranch));
|
|
125
|
+
return stack ? [stack] : [];
|
|
126
|
+
}
|
|
127
|
+
async function buildRestackSteps(stacks, cwd) {
|
|
128
|
+
const steps = [];
|
|
129
|
+
for (const stack of stacks) {
|
|
130
|
+
const ordered = topologicalOrder(stack);
|
|
131
|
+
for (const branch of ordered) {
|
|
132
|
+
if (branch.type === "root" || !branch.parent)
|
|
133
|
+
continue;
|
|
134
|
+
const mergeBase = await getMergeBase(branch.parent, branch.name, cwd);
|
|
135
|
+
steps.push({
|
|
136
|
+
branch: branch.name,
|
|
137
|
+
parent: branch.parent,
|
|
138
|
+
parentOldTip: mergeBase,
|
|
139
|
+
status: "pending",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return steps;
|
|
144
|
+
}
|
|
145
|
+
function topologicalOrder(stack) {
|
|
146
|
+
const result = [];
|
|
147
|
+
const root = stack.branches.find((b) => b.type === "root");
|
|
148
|
+
if (!root)
|
|
149
|
+
return result;
|
|
150
|
+
const childMap = new Map();
|
|
151
|
+
for (const branch of stack.branches) {
|
|
152
|
+
if (branch.parent) {
|
|
153
|
+
const children = childMap.get(branch.parent) ?? [];
|
|
154
|
+
children.push(branch);
|
|
155
|
+
childMap.set(branch.parent, children);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const queue = [root];
|
|
159
|
+
while (queue.length > 0) {
|
|
160
|
+
const current = queue.shift();
|
|
161
|
+
if (!current)
|
|
162
|
+
break;
|
|
163
|
+
result.push(current);
|
|
164
|
+
const children = childMap.get(current.name) ?? [];
|
|
165
|
+
queue.push(...children);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
async function getProgressPath(cwd) {
|
|
170
|
+
const dubDir = await getDubDir(cwd);
|
|
171
|
+
return path.join(dubDir, "restack-progress.json");
|
|
172
|
+
}
|
|
173
|
+
async function writeProgress(progress, cwd) {
|
|
174
|
+
const progressPath = await getProgressPath(cwd);
|
|
175
|
+
fs.writeFileSync(progressPath, `${JSON.stringify(progress, null, 2)}\n`);
|
|
176
|
+
}
|
|
177
|
+
async function readProgress(cwd) {
|
|
178
|
+
const progressPath = await getProgressPath(cwd);
|
|
179
|
+
if (!fs.existsSync(progressPath))
|
|
180
|
+
return null;
|
|
181
|
+
const raw = fs.readFileSync(progressPath, "utf-8");
|
|
182
|
+
return JSON.parse(raw);
|
|
183
|
+
}
|
|
184
|
+
async function clearProgress(cwd) {
|
|
185
|
+
const progressPath = await getProgressPath(cwd);
|
|
186
|
+
if (fs.existsSync(progressPath)) {
|
|
187
|
+
fs.unlinkSync(progressPath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=restack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restack.js","sourceRoot":"","sources":["../../src/commands/restack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface UndoResult {
|
|
2
|
+
undone: "create" | "restack";
|
|
3
|
+
details: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Undoes the last `dub create` or `dub restack` operation.
|
|
7
|
+
*
|
|
8
|
+
* Reversal strategy:
|
|
9
|
+
* - **create**: Deletes the created branch, restores state, checks out the previous branch.
|
|
10
|
+
* - **restack**: Resets every rebased branch to its pre-rebase tip via `git branch -f`,
|
|
11
|
+
* restores state, checks out the previous branch.
|
|
12
|
+
*
|
|
13
|
+
* Only one level of undo is supported. After undo, the undo entry is cleared.
|
|
14
|
+
*
|
|
15
|
+
* @param cwd - Working directory
|
|
16
|
+
* @returns What was undone and a human-readable summary
|
|
17
|
+
* @throws {DubError} If nothing to undo or working tree is dirty
|
|
18
|
+
*/
|
|
19
|
+
export declare function undo(cwd: string): Promise<UndoResult>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=undo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undo.d.ts","sourceRoot":"","sources":["../../src/commands/undo.ts"],"names":[],"mappings":"AAWA,UAAU,UAAU;IACnB,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA4D3D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DubError } from "../lib/errors.js";
|
|
2
|
+
import { checkoutBranch, deleteBranch, forceBranchTo, getCurrentBranch, isWorkingTreeClean, } from "../lib/git.js";
|
|
3
|
+
import { writeState } from "../lib/state.js";
|
|
4
|
+
import { clearUndoEntry, readUndoEntry } from "../lib/undo-log.js";
|
|
5
|
+
/**
|
|
6
|
+
* Undoes the last `dub create` or `dub restack` operation.
|
|
7
|
+
*
|
|
8
|
+
* Reversal strategy:
|
|
9
|
+
* - **create**: Deletes the created branch, restores state, checks out the previous branch.
|
|
10
|
+
* - **restack**: Resets every rebased branch to its pre-rebase tip via `git branch -f`,
|
|
11
|
+
* restores state, checks out the previous branch.
|
|
12
|
+
*
|
|
13
|
+
* Only one level of undo is supported. After undo, the undo entry is cleared.
|
|
14
|
+
*
|
|
15
|
+
* @param cwd - Working directory
|
|
16
|
+
* @returns What was undone and a human-readable summary
|
|
17
|
+
* @throws {DubError} If nothing to undo or working tree is dirty
|
|
18
|
+
*/
|
|
19
|
+
export async function undo(cwd) {
|
|
20
|
+
const entry = await readUndoEntry(cwd);
|
|
21
|
+
if (!(await isWorkingTreeClean(cwd))) {
|
|
22
|
+
throw new DubError("Working tree has uncommitted changes. Commit or stash them before undoing.");
|
|
23
|
+
}
|
|
24
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
25
|
+
if (entry.operation === "create") {
|
|
26
|
+
// If we're on a branch that's about to be deleted, switch away first
|
|
27
|
+
const needsCheckout = entry.createdBranches.includes(currentBranch);
|
|
28
|
+
if (needsCheckout) {
|
|
29
|
+
await checkoutBranch(entry.previousBranch, cwd);
|
|
30
|
+
}
|
|
31
|
+
for (const branch of entry.createdBranches) {
|
|
32
|
+
await deleteBranch(branch, cwd);
|
|
33
|
+
}
|
|
34
|
+
if (!needsCheckout && currentBranch !== entry.previousBranch) {
|
|
35
|
+
await checkoutBranch(entry.previousBranch, cwd);
|
|
36
|
+
}
|
|
37
|
+
await writeState(entry.previousState, cwd);
|
|
38
|
+
await clearUndoEntry(cwd);
|
|
39
|
+
return {
|
|
40
|
+
undone: "create",
|
|
41
|
+
details: `Deleted branch${entry.createdBranches.length > 1 ? "es" : ""} '${entry.createdBranches.join("', '")}'`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// restack undo: reset all branches to their pre-rebase tips
|
|
45
|
+
// First checkout a safe branch so we don't conflict with force-moves
|
|
46
|
+
await checkoutBranch(entry.previousBranch, cwd);
|
|
47
|
+
for (const [name, sha] of Object.entries(entry.branchTips)) {
|
|
48
|
+
if (name === entry.previousBranch)
|
|
49
|
+
continue; // skip the branch we're on
|
|
50
|
+
await forceBranchTo(name, sha, cwd);
|
|
51
|
+
}
|
|
52
|
+
// Now force the branch we're on (if it was tracked)
|
|
53
|
+
if (entry.branchTips[entry.previousBranch]) {
|
|
54
|
+
await forceBranchTo(entry.previousBranch, entry.branchTips[entry.previousBranch], cwd);
|
|
55
|
+
}
|
|
56
|
+
await writeState(entry.previousState, cwd);
|
|
57
|
+
await clearUndoEntry(cwd);
|
|
58
|
+
return {
|
|
59
|
+
undone: "restack",
|
|
60
|
+
details: `Reset ${Object.keys(entry.branchTips).length} branches to pre-restack state`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=undo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undo.js","sourceRoot":"","sources":["../../src/commands/undo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EACN,cAAc,EACd,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOnE;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW;IACrC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAEvC,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,QAAQ,CACjB,4EAA4E,CAC5E,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAElD,IAAI,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,qEAAqE;QACrE,MAAM,aAAa,GAAG,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACpE,IAAI,aAAa,EAAE,CAAC;YACnB,MAAM,cAAc,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;YAC5C,MAAM,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,CAAC,aAAa,IAAI,aAAa,KAAK,KAAK,CAAC,cAAc,EAAE,CAAC;YAC9D,MAAM,cAAc,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,UAAU,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;QAE1B,OAAO;YACN,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,iBAAiB,KAAK,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG;SAChH,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,qEAAqE;IACrE,MAAM,cAAc,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IAEhD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,IAAI,IAAI,KAAK,KAAK,CAAC,cAAc;YAAE,SAAS,CAAC,2BAA2B;QACxE,MAAM,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,oDAAoD;IACpD,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5C,MAAM,aAAa,CAClB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,EACtC,GAAG,CACH,CAAC;IACH,CAAC;IAED,MAAM,UAAU,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAE1B,OAAO;QACN,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,SAAS,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,gCAAgC;KACtF,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DubStack CLI — manage stacked diffs with ease.
|
|
4
|
+
*
|
|
5
|
+
* A local-first tool for managing chains of dependent git branches
|
|
6
|
+
* (stacked diffs) without manually dealing with complex rebase chains.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```bash
|
|
10
|
+
* dub init # Initialize in current repo
|
|
11
|
+
* dub create feat/my-branch # Create stacked branch
|
|
12
|
+
* dub log # View stack tree
|
|
13
|
+
* dub restack # Rebase stack onto updated parent
|
|
14
|
+
* dub undo # Undo last dub operation
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;GAgBG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DubStack CLI — manage stacked diffs with ease.
|
|
4
|
+
*
|
|
5
|
+
* A local-first tool for managing chains of dependent git branches
|
|
6
|
+
* (stacked diffs) without manually dealing with complex rebase chains.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```bash
|
|
10
|
+
* dub init # Initialize in current repo
|
|
11
|
+
* dub create feat/my-branch # Create stacked branch
|
|
12
|
+
* dub log # View stack tree
|
|
13
|
+
* dub restack # Rebase stack onto updated parent
|
|
14
|
+
* dub undo # Undo last dub operation
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
import { Command } from "commander";
|
|
22
|
+
import { create } from "./commands/create.js";
|
|
23
|
+
import { init } from "./commands/init.js";
|
|
24
|
+
import { log } from "./commands/log.js";
|
|
25
|
+
import { restack, restackContinue } from "./commands/restack.js";
|
|
26
|
+
import { undo } from "./commands/undo.js";
|
|
27
|
+
import { DubError } from "./lib/errors.js";
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const { version } = require("../package.json");
|
|
30
|
+
const program = new Command();
|
|
31
|
+
program
|
|
32
|
+
.name("dub")
|
|
33
|
+
.description("Manage stacked diffs (dependent git branches) with ease")
|
|
34
|
+
.version(version);
|
|
35
|
+
program
|
|
36
|
+
.command("init")
|
|
37
|
+
.description("Initialize DubStack in the current git repository")
|
|
38
|
+
.addHelpText("after", `
|
|
39
|
+
Examples:
|
|
40
|
+
$ dub init Initialize DubStack, creating .git/dubstack/ and updating .gitignore`)
|
|
41
|
+
.action(async () => {
|
|
42
|
+
const result = await init(process.cwd());
|
|
43
|
+
if (result.status === "created") {
|
|
44
|
+
console.log(chalk.green("✔ DubStack initialized"));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(chalk.yellow("⚠ DubStack already initialized"));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
program
|
|
51
|
+
.command("create")
|
|
52
|
+
.argument("<branch-name>", "Name of the new branch to create")
|
|
53
|
+
.description("Create a new branch stacked on top of the current branch")
|
|
54
|
+
.addHelpText("after", `
|
|
55
|
+
Examples:
|
|
56
|
+
$ dub create feat/api-endpoint Create a branch on top of current branch
|
|
57
|
+
$ dub create feat/ui-component Stack another branch on top`)
|
|
58
|
+
.action(async (branchName) => {
|
|
59
|
+
const result = await create(branchName, process.cwd());
|
|
60
|
+
console.log(chalk.green(`✔ Created branch '${result.branch}' on top of '${result.parent}'`));
|
|
61
|
+
});
|
|
62
|
+
program
|
|
63
|
+
.command("log")
|
|
64
|
+
.description("Display an ASCII tree of the current stack")
|
|
65
|
+
.addHelpText("after", `
|
|
66
|
+
Examples:
|
|
67
|
+
$ dub log Show the branch tree with current branch highlighted`)
|
|
68
|
+
.action(async () => {
|
|
69
|
+
const output = await log(process.cwd());
|
|
70
|
+
// Apply chalk styling to the output
|
|
71
|
+
const styled = output
|
|
72
|
+
.replace(/\*(.+?) \(Current\)\*/g, chalk.bold.cyan("$1 (Current)"))
|
|
73
|
+
.replace(/⚠ \(missing\)/g, chalk.yellow("⚠ (missing)"));
|
|
74
|
+
console.log(styled);
|
|
75
|
+
});
|
|
76
|
+
program
|
|
77
|
+
.command("restack")
|
|
78
|
+
.description("Rebase all branches in the stack onto their updated parents")
|
|
79
|
+
.option("--continue", "Continue restacking after resolving conflicts")
|
|
80
|
+
.addHelpText("after", `
|
|
81
|
+
Examples:
|
|
82
|
+
$ dub restack Rebase the current stack
|
|
83
|
+
$ dub restack --continue Continue after resolving conflicts`)
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
const result = options.continue
|
|
86
|
+
? await restackContinue(process.cwd())
|
|
87
|
+
: await restack(process.cwd());
|
|
88
|
+
if (result.status === "up-to-date") {
|
|
89
|
+
console.log(chalk.green("✔ Stack is already up to date"));
|
|
90
|
+
}
|
|
91
|
+
else if (result.status === "conflict") {
|
|
92
|
+
console.log(chalk.yellow(`⚠ Conflict while restacking '${result.conflictBranch}'`));
|
|
93
|
+
console.log(chalk.dim(" Resolve conflicts, stage changes, then run: dub restack --continue"));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(chalk.green(`✔ Restacked ${result.rebased.length} branch(es)`));
|
|
97
|
+
for (const branch of result.rebased) {
|
|
98
|
+
console.log(chalk.dim(` ↳ ${branch}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
program
|
|
103
|
+
.command("undo")
|
|
104
|
+
.description("Undo the last dub create or dub restack operation")
|
|
105
|
+
.addHelpText("after", `
|
|
106
|
+
Examples:
|
|
107
|
+
$ dub undo Roll back the last dub operation`)
|
|
108
|
+
.action(async () => {
|
|
109
|
+
const result = await undo(process.cwd());
|
|
110
|
+
console.log(chalk.green(`✔ Undid '${result.undone}': ${result.details}`));
|
|
111
|
+
});
|
|
112
|
+
async function main() {
|
|
113
|
+
try {
|
|
114
|
+
await program.parseAsync(process.argv);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof DubError) {
|
|
118
|
+
console.error(chalk.red(`✖ ${error.message}`));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
main();
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all user-facing DubStack errors.
|
|
3
|
+
*
|
|
4
|
+
* The CLI entry point catches instances of this class and prints
|
|
5
|
+
* a clean, colored error message. Unknown errors get a full stack trace.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* throw new DubError("Branch 'feat/x' already exists")
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare class DubError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,QAAS,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI3B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all user-facing DubStack errors.
|
|
3
|
+
*
|
|
4
|
+
* The CLI entry point catches instances of this class and prints
|
|
5
|
+
* a clean, colored error message. Unknown errors get a full stack trace.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* throw new DubError("Branch 'feat/x' already exists")
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export class DubError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "DubError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAClC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACxB,CAAC;CACD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the given directory is inside a git repository.
|
|
3
|
+
* @returns `true` if inside a git worktree, `false` otherwise. Never throws.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isGitRepo(cwd: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the absolute path to the repository root.
|
|
8
|
+
* @throws {DubError} If not inside a git repository.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getRepoRoot(cwd: string): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the name of the currently checked-out branch.
|
|
13
|
+
* @throws {DubError} If HEAD is detached or the repo has no commits.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCurrentBranch(cwd: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether a branch with the given name exists locally.
|
|
18
|
+
* @returns `true` if the branch exists, `false` otherwise. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function branchExists(name: string, cwd: string): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new branch and switches to it.
|
|
23
|
+
* @throws {DubError} If a branch with that name already exists.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createBranch(name: string, cwd: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Switches to an existing branch.
|
|
28
|
+
* @throws {DubError} If the branch does not exist.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkoutBranch(name: string, cwd: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Deletes a local branch forcefully. Used by undo to remove created branches.
|
|
33
|
+
* @throws {DubError} If the branch does not exist.
|
|
34
|
+
*/
|
|
35
|
+
export declare function deleteBranch(name: string, cwd: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Force-moves a branch pointer to a specific commit SHA.
|
|
38
|
+
* Used by undo to reset branches to their pre-operation tips.
|
|
39
|
+
*/
|
|
40
|
+
export declare function forceBranchTo(name: string, sha: string, cwd: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether the working tree is clean (no uncommitted changes).
|
|
43
|
+
* @returns `true` if clean (no output from `git status --porcelain`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function isWorkingTreeClean(cwd: string): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Performs `git rebase --onto` to move a branch from one base to another.
|
|
48
|
+
*
|
|
49
|
+
* @param newBase - The commit/branch to rebase onto
|
|
50
|
+
* @param oldBase - The old base commit to replay from
|
|
51
|
+
* @param branch - The branch being rebased
|
|
52
|
+
* @throws {DubError} If a merge conflict occurs during rebase
|
|
53
|
+
*/
|
|
54
|
+
export declare function rebaseOnto(newBase: string, oldBase: string, branch: string, cwd: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Continues an in-progress rebase after conflicts have been resolved.
|
|
57
|
+
* @throws {DubError} If the rebase continue fails.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rebaseContinue(cwd: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Returns the merge-base (common ancestor) commit SHA of two branches.
|
|
62
|
+
*/
|
|
63
|
+
export declare function getMergeBase(a: string, b: string, cwd: string): Promise<string>;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the commit SHA at the tip of a branch.
|
|
66
|
+
* @throws {DubError} If the branch does not exist.
|
|
67
|
+
*/
|
|
68
|
+
export declare function getBranchTip(name: string, cwd: string): Promise<string>;
|
|
69
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW7D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW9D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBnE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CACjC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3E;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3E;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/D;AAED;;GAEG;AACH,wBAAsB,YAAY,CACjC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7E"}
|