@solaqua/gji 0.2.0 → 0.2.1
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/dist/clean.d.ts +1 -0
- package/dist/clean.js +18 -4
- package/dist/cli.js +12 -4
- package/dist/go.js +7 -3
- package/dist/new.d.ts +1 -0
- package/dist/new.js +10 -0
- package/dist/pr.d.ts +1 -0
- package/dist/pr.js +11 -0
- package/dist/remove.d.ts +1 -0
- package/dist/remove.js +12 -2
- package/dist/sync.d.ts +1 -0
- package/dist/sync.js +45 -7
- package/package.json +1 -1
package/dist/clean.d.ts
CHANGED
package/dist/clean.js
CHANGED
|
@@ -14,7 +14,7 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
14
14
|
emitError(options, 'No linked worktrees to clean');
|
|
15
15
|
return 1;
|
|
16
16
|
}
|
|
17
|
-
if (!options.force && (options.json || isHeadless())) {
|
|
17
|
+
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
18
18
|
const message = '--force is required';
|
|
19
19
|
if (options.json) {
|
|
20
20
|
emitError(options, message);
|
|
@@ -24,8 +24,9 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
return 1;
|
|
26
26
|
}
|
|
27
|
-
// With --force, skip selection prompt and target all candidates.
|
|
28
|
-
const
|
|
27
|
+
// With --force, or dry-run in headless/json mode, skip selection prompt and target all candidates.
|
|
28
|
+
const shouldSelectAll = options.force || (options.dryRun && (options.json || isHeadless()));
|
|
29
|
+
const selections = shouldSelectAll
|
|
29
30
|
? cleanupCandidates.map((w) => w.path)
|
|
30
31
|
: await promptForWorktrees(cleanupCandidates);
|
|
31
32
|
if (!selections || selections.length === 0) {
|
|
@@ -37,10 +38,23 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
37
38
|
options.stderr('Selected worktree no longer exists\n');
|
|
38
39
|
return 1;
|
|
39
40
|
}
|
|
40
|
-
if (!options.force && !(await confirmRemoval(selectedWorktrees))) {
|
|
41
|
+
if (!options.dryRun && !options.force && !(await confirmRemoval(selectedWorktrees))) {
|
|
41
42
|
options.stderr('Aborted\n');
|
|
42
43
|
return 1;
|
|
43
44
|
}
|
|
45
|
+
if (options.dryRun) {
|
|
46
|
+
if (options.json) {
|
|
47
|
+
const removed = selectedWorktrees.map((w) => ({ branch: w.branch, path: w.path }));
|
|
48
|
+
options.stdout(`${JSON.stringify({ removed, dryRun: true }, null, 2)}\n`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
for (const w of selectedWorktrees) {
|
|
52
|
+
const desc = w.branch ? `branch: ${w.branch}` : 'detached';
|
|
53
|
+
options.stdout(`Would remove worktree at ${w.path} (${desc})\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
44
58
|
const removedPaths = [];
|
|
45
59
|
const removedWorktrees = [];
|
|
46
60
|
for (const worktree of selectedWorktrees) {
|
package/dist/cli.js
CHANGED
|
@@ -59,6 +59,7 @@ function registerCommands(program) {
|
|
|
59
59
|
.command('new [branch]')
|
|
60
60
|
.description('create a new branch or detached linked worktree')
|
|
61
61
|
.option('--detached', 'create a detached worktree without a branch')
|
|
62
|
+
.option('--dry-run', 'show what would be created without executing any git commands or writing files')
|
|
62
63
|
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
63
64
|
.action(notImplemented('new'));
|
|
64
65
|
program
|
|
@@ -67,8 +68,9 @@ function registerCommands(program) {
|
|
|
67
68
|
.option('--write', 'write the integration to the shell config file')
|
|
68
69
|
.action(notImplemented('init'));
|
|
69
70
|
program
|
|
70
|
-
.command('pr <
|
|
71
|
-
.description('fetch a pull request
|
|
71
|
+
.command('pr <ref>')
|
|
72
|
+
.description('fetch a pull request by number, #number, or URL into a linked worktree')
|
|
73
|
+
.option('--dry-run', 'show what would be created without executing any git commands or writing files')
|
|
72
74
|
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
73
75
|
.action(notImplemented('pr'));
|
|
74
76
|
program
|
|
@@ -90,6 +92,7 @@ function registerCommands(program) {
|
|
|
90
92
|
.command('sync')
|
|
91
93
|
.description('fetch and update one or all worktrees')
|
|
92
94
|
.option('--all', 'sync every worktree in the repository')
|
|
95
|
+
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
93
96
|
.action(notImplemented('sync'));
|
|
94
97
|
program
|
|
95
98
|
.command('ls')
|
|
@@ -100,6 +103,7 @@ function registerCommands(program) {
|
|
|
100
103
|
.command('clean')
|
|
101
104
|
.description('interactively prune linked worktrees')
|
|
102
105
|
.option('-f, --force', 'bypass prompts, force-remove dirty worktrees, and force-delete unmerged branches')
|
|
106
|
+
.option('--dry-run', 'show what would be deleted without removing anything')
|
|
103
107
|
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
104
108
|
.action(notImplemented('clean'));
|
|
105
109
|
program
|
|
@@ -107,6 +111,7 @@ function registerCommands(program) {
|
|
|
107
111
|
.alias('rm')
|
|
108
112
|
.description('remove a linked worktree and delete its branch when present')
|
|
109
113
|
.option('-f, --force', 'bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch')
|
|
114
|
+
.option('--dry-run', 'show what would be deleted without removing anything')
|
|
110
115
|
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
111
116
|
.action(notImplemented('remove'));
|
|
112
117
|
const configCommand = program
|
|
@@ -130,7 +135,7 @@ function attachCommandActions(program, options) {
|
|
|
130
135
|
program.commands
|
|
131
136
|
.find((command) => command.name() === 'new')
|
|
132
137
|
?.action(async (branch, commandOptions) => {
|
|
133
|
-
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, json: commandOptions.json });
|
|
138
|
+
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, dryRun: commandOptions.dryRun, json: commandOptions.json });
|
|
134
139
|
if (exitCode !== 0) {
|
|
135
140
|
throw commanderExit(exitCode);
|
|
136
141
|
}
|
|
@@ -151,7 +156,7 @@ function attachCommandActions(program, options) {
|
|
|
151
156
|
program.commands
|
|
152
157
|
.find((command) => command.name() === 'pr')
|
|
153
158
|
?.action(async (number, commandOptions) => {
|
|
154
|
-
const exitCode = await runPrCommand({ cwd: options.cwd, json: commandOptions.json, number, stderr: options.stderr, stdout: options.stdout });
|
|
159
|
+
const exitCode = await runPrCommand({ cwd: options.cwd, dryRun: commandOptions.dryRun, json: commandOptions.json, number, stderr: options.stderr, stdout: options.stdout });
|
|
155
160
|
if (exitCode !== 0) {
|
|
156
161
|
throw commanderExit(exitCode);
|
|
157
162
|
}
|
|
@@ -200,6 +205,7 @@ function attachCommandActions(program, options) {
|
|
|
200
205
|
const exitCode = await runSyncCommand({
|
|
201
206
|
all: commandOptions.all,
|
|
202
207
|
cwd: options.cwd,
|
|
208
|
+
json: commandOptions.json,
|
|
203
209
|
stderr: options.stderr,
|
|
204
210
|
stdout: options.stdout,
|
|
205
211
|
});
|
|
@@ -224,6 +230,7 @@ function attachCommandActions(program, options) {
|
|
|
224
230
|
?.action(async (commandOptions) => {
|
|
225
231
|
const exitCode = await runCleanCommand({
|
|
226
232
|
cwd: options.cwd,
|
|
233
|
+
dryRun: commandOptions.dryRun,
|
|
227
234
|
force: commandOptions.force,
|
|
228
235
|
json: commandOptions.json,
|
|
229
236
|
stderr: options.stderr,
|
|
@@ -237,6 +244,7 @@ function attachCommandActions(program, options) {
|
|
|
237
244
|
const exitCode = await runRemoveCommand({
|
|
238
245
|
branch,
|
|
239
246
|
cwd: options.cwd,
|
|
247
|
+
dryRun: commandOptions.dryRun,
|
|
240
248
|
force: commandOptions.force,
|
|
241
249
|
json: commandOptions.json,
|
|
242
250
|
stderr: options.stderr,
|
package/dist/go.js
CHANGED
|
@@ -22,9 +22,13 @@ export function createGoCommand(dependencies = {}) {
|
|
|
22
22
|
? worktrees.find((entry) => entry.branch === options.branch)?.path
|
|
23
23
|
: prompted ?? undefined;
|
|
24
24
|
if (!resolvedPath) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
: '
|
|
25
|
+
if (options.branch) {
|
|
26
|
+
options.stderr(`No worktree found for branch: ${options.branch}\n`);
|
|
27
|
+
options.stderr(`Hint: Use 'gji ls' to see available worktrees\n`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
options.stderr('Aborted\n');
|
|
31
|
+
}
|
|
28
32
|
return 1;
|
|
29
33
|
}
|
|
30
34
|
const chosenWorktree = worktrees.find((w) => w.path === resolvedPath);
|
package/dist/new.d.ts
CHANGED
package/dist/new.js
CHANGED
|
@@ -57,6 +57,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
59
|
options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
60
|
+
options.stderr(`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree\n`);
|
|
60
61
|
}
|
|
61
62
|
return 1;
|
|
62
63
|
}
|
|
@@ -68,6 +69,15 @@ export function createNewCommand(dependencies = {}) {
|
|
|
68
69
|
options.stderr(`Aborted because target worktree path already exists: ${worktreePath}\n`);
|
|
69
70
|
return 1;
|
|
70
71
|
}
|
|
72
|
+
if (options.dryRun) {
|
|
73
|
+
if (options.json) {
|
|
74
|
+
options.stdout(`${JSON.stringify({ branch: worktreeName, path: worktreePath, dryRun: true }, null, 2)}\n`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
options.stdout(`Would create worktree at ${worktreePath} (branch: ${worktreeName})\n`);
|
|
78
|
+
}
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
71
81
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
72
82
|
const gitArgs = options.detached
|
|
73
83
|
? ['worktree', 'add', '--detach', worktreePath]
|
package/dist/pr.d.ts
CHANGED
package/dist/pr.js
CHANGED
|
@@ -50,6 +50,7 @@ export function createPrCommand(dependencies = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
options.stderr(`gji pr: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
53
|
+
options.stderr(`Hint: Use 'gji remove pr/${prNumber}' or 'gji clean' to remove the existing worktree\n`);
|
|
53
54
|
}
|
|
54
55
|
return 1;
|
|
55
56
|
}
|
|
@@ -61,6 +62,15 @@ export function createPrCommand(dependencies = {}) {
|
|
|
61
62
|
options.stderr(`Aborted because target worktree path already exists: ${worktreePath}\n`);
|
|
62
63
|
return 1;
|
|
63
64
|
}
|
|
65
|
+
if (options.dryRun) {
|
|
66
|
+
if (options.json) {
|
|
67
|
+
options.stdout(`${JSON.stringify({ branch: branchName, path: worktreePath, dryRun: true }, null, 2)}\n`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
options.stdout(`Would create worktree at ${worktreePath} (branch: ${branchName})\n`);
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
64
74
|
try {
|
|
65
75
|
await execFileAsync('git', ['fetch', 'origin', `refs/pull/${prNumber}/head:${remoteRef}`], { cwd: repository.repoRoot });
|
|
66
76
|
}
|
|
@@ -71,6 +81,7 @@ export function createPrCommand(dependencies = {}) {
|
|
|
71
81
|
}
|
|
72
82
|
else {
|
|
73
83
|
options.stderr(`${message}\n`);
|
|
84
|
+
options.stderr(`Hint: Verify the remote is reachable: git fetch origin\n`);
|
|
74
85
|
}
|
|
75
86
|
return 1;
|
|
76
87
|
}
|
package/dist/remove.d.ts
CHANGED
package/dist/remove.js
CHANGED
|
@@ -38,7 +38,7 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
38
38
|
emitError(options, `No linked worktree found for branch: ${selection}`);
|
|
39
39
|
return 1;
|
|
40
40
|
}
|
|
41
|
-
if (!options.force && (options.json || isHeadless())) {
|
|
41
|
+
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
42
42
|
const message = '--force is required';
|
|
43
43
|
if (options.json) {
|
|
44
44
|
emitError(options, message);
|
|
@@ -48,10 +48,20 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
return 1;
|
|
50
50
|
}
|
|
51
|
-
if (!options.force && !(await confirmRemoval(worktree))) {
|
|
51
|
+
if (!options.dryRun && !options.force && !(await confirmRemoval(worktree))) {
|
|
52
52
|
options.stderr('Aborted\n');
|
|
53
53
|
return 1;
|
|
54
54
|
}
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
if (options.json) {
|
|
57
|
+
options.stdout(`${JSON.stringify({ branch: worktree.branch, path: worktree.path, dryRun: true }, null, 2)}\n`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const desc = worktree.branch ? `branch: ${worktree.branch}` : 'detached';
|
|
61
|
+
options.stdout(`Would remove worktree at ${worktree.path} (${desc})\n`);
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
55
65
|
const config = await loadEffectiveConfig(repository.repoRoot);
|
|
56
66
|
const hooks = extractHooks(config);
|
|
57
67
|
await runHook(hooks.beforeRemove, worktree.path, { branch: worktree.branch ?? undefined, path: worktree.path, repo: basename(repository.repoRoot) }, options.stderr);
|
package/dist/sync.d.ts
CHANGED
package/dist/sync.js
CHANGED
|
@@ -7,24 +7,47 @@ export async function runSyncCommand(options) {
|
|
|
7
7
|
const config = await loadEffectiveConfig(repository.repoRoot);
|
|
8
8
|
const worktrees = await listWorktrees(options.cwd);
|
|
9
9
|
const remote = resolveConfiguredString(config.syncRemote) ?? 'origin';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
let defaultBranch;
|
|
11
|
+
try {
|
|
12
|
+
defaultBranch = resolveConfiguredString(config.syncDefaultBranch)
|
|
13
|
+
?? await resolveDefaultBranch(repository.repoRoot, remote);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
emitError(options, `Unable to reach remote '${remote}'`);
|
|
17
|
+
if (!options.json) {
|
|
18
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
19
|
+
}
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
12
22
|
if (!defaultBranch) {
|
|
13
|
-
options
|
|
23
|
+
emitError(options, 'Unable to determine the default branch for sync.');
|
|
24
|
+
if (!options.json) {
|
|
25
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
26
|
+
}
|
|
14
27
|
return 1;
|
|
15
28
|
}
|
|
16
29
|
const targetWorktrees = selectTargetWorktrees(worktrees, repository.currentRoot, options.all);
|
|
17
30
|
if (targetWorktrees === 'detached') {
|
|
18
|
-
options
|
|
31
|
+
emitError(options, `Cannot sync detached worktree: ${repository.currentRoot}`);
|
|
19
32
|
return 1;
|
|
20
33
|
}
|
|
21
34
|
for (const worktree of targetWorktrees) {
|
|
22
35
|
if (await isDirtyWorktree(worktree.path)) {
|
|
23
|
-
options
|
|
36
|
+
emitError(options, `Cannot sync dirty worktree: ${worktree.path}`);
|
|
24
37
|
return 1;
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
|
-
|
|
40
|
+
try {
|
|
41
|
+
await runGit(repository.repoRoot, ['fetch', '--prune', remote]);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
emitError(options, `Failed to fetch from remote '${remote}'`);
|
|
45
|
+
if (!options.json) {
|
|
46
|
+
options.stderr(`Hint: Add the remote with: git remote add ${remote} <url>\n`);
|
|
47
|
+
}
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const updatedWorktrees = [];
|
|
28
51
|
for (const worktree of targetWorktrees) {
|
|
29
52
|
if (worktree.branch === defaultBranch) {
|
|
30
53
|
await runGit(worktree.path, ['merge', '--ff-only', `${remote}/${defaultBranch}`]);
|
|
@@ -32,10 +55,25 @@ export async function runSyncCommand(options) {
|
|
|
32
55
|
else {
|
|
33
56
|
await runGit(worktree.path, ['rebase', `${remote}/${defaultBranch}`]);
|
|
34
57
|
}
|
|
35
|
-
|
|
58
|
+
updatedWorktrees.push(worktree);
|
|
59
|
+
if (!options.json) {
|
|
60
|
+
options.stdout(`${worktree.path}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (options.json) {
|
|
64
|
+
const updated = updatedWorktrees.map((w) => ({ branch: w.branch, path: w.path }));
|
|
65
|
+
options.stdout(`${JSON.stringify({ updated }, null, 2)}\n`);
|
|
36
66
|
}
|
|
37
67
|
return 0;
|
|
38
68
|
}
|
|
69
|
+
function emitError(options, message) {
|
|
70
|
+
if (options.json) {
|
|
71
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
options.stderr(`${message}\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
39
77
|
function selectTargetWorktrees(worktrees, currentRoot, all) {
|
|
40
78
|
if (all) {
|
|
41
79
|
return worktrees
|