@solaqua/gji 0.6.2 → 0.7.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 +26 -1
- package/dist/back.d.ts +1 -1
- package/dist/back.js +23 -17
- package/dist/clean.d.ts +1 -1
- package/dist/clean.js +44 -35
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +264 -164
- package/dist/completion.js +3 -3
- package/dist/config-command.js +5 -5
- package/dist/config.js +41 -35
- package/dist/conflict.d.ts +1 -1
- package/dist/conflict.js +14 -6
- package/dist/editor.js +29 -9
- package/dist/file-sync.d.ts +1 -0
- package/dist/file-sync.js +15 -11
- package/dist/git.d.ts +1 -1
- package/dist/git.js +21 -19
- package/dist/gji-bundle.mjs +1624 -819
- package/dist/go.d.ts +2 -2
- package/dist/go.js +39 -26
- package/dist/headless.js +1 -1
- package/dist/history-command.js +3 -3
- package/dist/history.js +12 -12
- package/dist/hooks.js +16 -16
- package/dist/index.js +13 -9
- package/dist/init.d.ts +2 -2
- package/dist/init.js +106 -94
- package/dist/install-prompt.d.ts +3 -3
- package/dist/install-prompt.js +46 -28
- package/dist/ls.d.ts +2 -2
- package/dist/ls.js +29 -29
- package/dist/new.d.ts +2 -2
- package/dist/new.js +96 -81
- package/dist/open.d.ts +2 -2
- package/dist/open.js +24 -21
- package/dist/package-manager.js +96 -45
- package/dist/pr.d.ts +2 -2
- package/dist/pr.js +47 -34
- package/dist/remove.d.ts +1 -1
- package/dist/remove.js +39 -27
- package/dist/repo-registry.js +14 -14
- package/dist/repo.js +29 -28
- package/dist/root.js +3 -3
- package/dist/shell-completion.d.ts +1 -1
- package/dist/shell-completion.js +65 -37
- package/dist/shell-handoff.js +2 -2
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +4 -4
- package/dist/status.d.ts +5 -5
- package/dist/status.js +23 -23
- package/dist/sync-files-command.d.ts +10 -0
- package/dist/sync-files-command.js +137 -0
- package/dist/sync.js +23 -15
- package/dist/trigger-hook.js +9 -5
- package/dist/warp.js +37 -33
- package/dist/worktree-info.d.ts +9 -9
- package/dist/worktree-info.js +31 -29
- package/dist/worktree-management.d.ts +1 -1
- package/dist/worktree-management.js +26 -11
- package/dist/worktree-prompts.js +5 -5
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +23 -0
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +8 -2
package/dist/warp.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { readWorktreeHealth } from
|
|
5
|
-
import { isHeadless } from
|
|
6
|
-
import { appendHistory } from
|
|
7
|
-
import { runNewCommand } from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { writeShellOutput } from
|
|
11
|
-
const WARP_OUTPUT_FILE_ENV =
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { isCancel, select } from "@clack/prompts";
|
|
4
|
+
import { readWorktreeHealth } from "./git.js";
|
|
5
|
+
import { isHeadless } from "./headless.js";
|
|
6
|
+
import { appendHistory } from "./history.js";
|
|
7
|
+
import { runNewCommand } from "./new.js";
|
|
8
|
+
import { listWorktrees } from "./repo.js";
|
|
9
|
+
import { loadRegistry } from "./repo-registry.js";
|
|
10
|
+
import { writeShellOutput } from "./shell-handoff.js";
|
|
11
|
+
const WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
12
12
|
export async function runWarpCommand(options) {
|
|
13
13
|
if (options.newWorktree) {
|
|
14
14
|
const registry = await loadRegistry();
|
|
15
15
|
if (registry.length === 0) {
|
|
16
|
-
options.stderr(
|
|
17
|
-
|
|
16
|
+
options.stderr("gji warp: no repos registered yet.\n" +
|
|
17
|
+
"Use any gji command in a repository to register it automatically.\n");
|
|
18
18
|
return 1;
|
|
19
19
|
}
|
|
20
20
|
return runWarpNew(options, registry);
|
|
@@ -23,16 +23,20 @@ export async function runWarpCommand(options) {
|
|
|
23
23
|
}
|
|
24
24
|
async function runWarpNavigate(options) {
|
|
25
25
|
if ((isHeadless() || options.json) && !options.branch) {
|
|
26
|
-
const message =
|
|
26
|
+
const message = "branch argument is required";
|
|
27
27
|
if (options.json) {
|
|
28
28
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
|
-
options.stderr(
|
|
31
|
+
options.stderr("gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
32
32
|
}
|
|
33
33
|
return 1;
|
|
34
34
|
}
|
|
35
|
-
const target = await resolveWarpTarget({
|
|
35
|
+
const target = await resolveWarpTarget({
|
|
36
|
+
...options,
|
|
37
|
+
commandName: "gji warp",
|
|
38
|
+
json: options.json,
|
|
39
|
+
});
|
|
36
40
|
if (!target)
|
|
37
41
|
return 1;
|
|
38
42
|
if (options.json) {
|
|
@@ -52,11 +56,11 @@ async function runWarpNew(options, registry) {
|
|
|
52
56
|
}
|
|
53
57
|
else {
|
|
54
58
|
if (isHeadless()) {
|
|
55
|
-
options.stderr(
|
|
59
|
+
options.stderr("gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
56
60
|
return 1;
|
|
57
61
|
}
|
|
58
62
|
const choice = await select({
|
|
59
|
-
message:
|
|
63
|
+
message: "Create worktree in which repo?",
|
|
60
64
|
options: deduplicatedRegistry.map((entry) => ({
|
|
61
65
|
value: entry.path,
|
|
62
66
|
label: entry.name,
|
|
@@ -64,7 +68,7 @@ async function runWarpNew(options, registry) {
|
|
|
64
68
|
})),
|
|
65
69
|
});
|
|
66
70
|
if (isCancel(choice)) {
|
|
67
|
-
options.stderr(
|
|
71
|
+
options.stderr("Aborted\n");
|
|
68
72
|
return 1;
|
|
69
73
|
}
|
|
70
74
|
targetRepoRoot = choice;
|
|
@@ -81,7 +85,7 @@ async function runWarpNew(options, registry) {
|
|
|
81
85
|
// runNewCommand writes the created path to options.stdout via writeShellOutput.
|
|
82
86
|
// Since GJI_NEW_OUTPUT_FILE is not set in the warp shell context, it falls
|
|
83
87
|
// through to our captured stdout, giving us the path to hand off.
|
|
84
|
-
let capturedPath =
|
|
88
|
+
let capturedPath = "";
|
|
85
89
|
const captureStdout = (chunk) => {
|
|
86
90
|
capturedPath = chunk.trim();
|
|
87
91
|
};
|
|
@@ -95,7 +99,7 @@ async function runWarpNew(options, registry) {
|
|
|
95
99
|
return exitCode;
|
|
96
100
|
}
|
|
97
101
|
if (!capturedPath) {
|
|
98
|
-
options.stderr(
|
|
102
|
+
options.stderr("gji warp: could not determine new worktree path\n");
|
|
99
103
|
return 1;
|
|
100
104
|
}
|
|
101
105
|
await writeShellOutput(WARP_OUTPUT_FILE_ENV, capturedPath, options.stdout);
|
|
@@ -127,7 +131,7 @@ async function canonicalizeRepoPath(repoPath) {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
function findByQuery(items, query) {
|
|
130
|
-
const slashIdx = query.indexOf(
|
|
134
|
+
const slashIdx = query.indexOf("/");
|
|
131
135
|
if (slashIdx !== -1) {
|
|
132
136
|
const repoQuery = query.slice(0, slashIdx);
|
|
133
137
|
const branchQuery = query.slice(slashIdx + 1);
|
|
@@ -138,7 +142,7 @@ function findByQuery(items, query) {
|
|
|
138
142
|
return items.find((item) => item.worktree.branch === query) ?? null;
|
|
139
143
|
}
|
|
140
144
|
export async function resolveWarpTarget(options) {
|
|
141
|
-
const cmd = options.commandName ??
|
|
145
|
+
const cmd = options.commandName ?? "gji";
|
|
142
146
|
const emitError = (message, hint) => {
|
|
143
147
|
if (options.json) {
|
|
144
148
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
@@ -151,7 +155,7 @@ export async function resolveWarpTarget(options) {
|
|
|
151
155
|
};
|
|
152
156
|
const registry = await loadRegistry();
|
|
153
157
|
if (registry.length === 0) {
|
|
154
|
-
emitError(
|
|
158
|
+
emitError("not in a git repository and no repos registered yet.", "Use any gji command inside a repository to register it.\n");
|
|
155
159
|
return null;
|
|
156
160
|
}
|
|
157
161
|
const results = await Promise.allSettled(registry.map(async (entry) => {
|
|
@@ -160,7 +164,7 @@ export async function resolveWarpTarget(options) {
|
|
|
160
164
|
}));
|
|
161
165
|
const allItems = [];
|
|
162
166
|
for (const result of results) {
|
|
163
|
-
if (result.status ===
|
|
167
|
+
if (result.status === "rejected")
|
|
164
168
|
continue;
|
|
165
169
|
const { repoName, worktrees } = result.value;
|
|
166
170
|
for (const worktree of worktrees) {
|
|
@@ -168,7 +172,7 @@ export async function resolveWarpTarget(options) {
|
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
if (allItems.length === 0) {
|
|
171
|
-
emitError(
|
|
175
|
+
emitError("no accessible worktrees found in any registered repo.");
|
|
172
176
|
return null;
|
|
173
177
|
}
|
|
174
178
|
if (options.branch) {
|
|
@@ -181,7 +185,7 @@ export async function resolveWarpTarget(options) {
|
|
|
181
185
|
}
|
|
182
186
|
const path = await promptForWarpTarget(allItems);
|
|
183
187
|
if (!path) {
|
|
184
|
-
options.stderr(
|
|
188
|
+
options.stderr("Aborted\n");
|
|
185
189
|
return null;
|
|
186
190
|
}
|
|
187
191
|
const chosen = allItems.find((item) => item.worktree.path === path);
|
|
@@ -190,11 +194,11 @@ export async function resolveWarpTarget(options) {
|
|
|
190
194
|
async function promptForWarpTarget(items) {
|
|
191
195
|
const healthResults = await Promise.allSettled(items.map((item) => readWorktreeHealth(item.worktree.path)));
|
|
192
196
|
const choice = await select({
|
|
193
|
-
message:
|
|
197
|
+
message: "Warp to a worktree",
|
|
194
198
|
options: items.map((item, i) => {
|
|
195
|
-
const health = healthResults[i].status ===
|
|
199
|
+
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
196
200
|
const upstream = health ? formatHint(item.worktree.branch, health) : null;
|
|
197
|
-
const label = `${item.repoName} › ${item.worktree.branch ??
|
|
201
|
+
const label = `${item.repoName} › ${item.worktree.branch ?? "(detached)"}`;
|
|
198
202
|
const pathHint = item.worktree.isCurrent
|
|
199
203
|
? `${item.worktree.path} (current)`
|
|
200
204
|
: item.worktree.path;
|
|
@@ -211,11 +215,11 @@ function formatHint(branch, health) {
|
|
|
211
215
|
if (branch === null)
|
|
212
216
|
return null;
|
|
213
217
|
if (!health.hasUpstream)
|
|
214
|
-
return
|
|
218
|
+
return "no upstream";
|
|
215
219
|
if (health.upstreamGone)
|
|
216
|
-
return
|
|
220
|
+
return "upstream gone";
|
|
217
221
|
if (health.ahead === 0 && health.behind === 0)
|
|
218
|
-
return
|
|
222
|
+
return "up to date";
|
|
219
223
|
if (health.ahead === 0)
|
|
220
224
|
return `behind ${health.behind}`;
|
|
221
225
|
if (health.behind === 0)
|
package/dist/worktree-info.d.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { type WorktreeHealth } from
|
|
2
|
-
import type { WorktreeEntry } from
|
|
1
|
+
import { type WorktreeHealth } from "./git.js";
|
|
2
|
+
import type { WorktreeEntry } from "./repo.js";
|
|
3
3
|
export interface WorktreeInfo extends WorktreeEntry {
|
|
4
4
|
lastCommitTimestamp: number | null;
|
|
5
|
-
status: WorktreeHealth[
|
|
5
|
+
status: WorktreeHealth["status"] | "unknown";
|
|
6
6
|
upstream: UpstreamState;
|
|
7
7
|
}
|
|
8
8
|
export interface SerializedWorktreeInfo {
|
|
9
9
|
branch: string | null;
|
|
10
10
|
lastCommitTimestamp: number | null;
|
|
11
11
|
path: string;
|
|
12
|
-
status: WorktreeInfo[
|
|
12
|
+
status: WorktreeInfo["status"];
|
|
13
13
|
upstream: UpstreamState;
|
|
14
14
|
}
|
|
15
15
|
export type UpstreamState = {
|
|
16
|
-
kind:
|
|
16
|
+
kind: "detached";
|
|
17
17
|
} | {
|
|
18
|
-
kind:
|
|
18
|
+
kind: "no-upstream";
|
|
19
19
|
} | {
|
|
20
|
-
kind:
|
|
20
|
+
kind: "stale";
|
|
21
21
|
} | {
|
|
22
|
-
kind:
|
|
22
|
+
kind: "tracked";
|
|
23
23
|
ahead: number;
|
|
24
24
|
behind: number;
|
|
25
25
|
} | {
|
|
26
|
-
kind:
|
|
26
|
+
kind: "unknown";
|
|
27
27
|
};
|
|
28
28
|
export declare function readWorktreeInfos(worktrees: WorktreeEntry[]): Promise<WorktreeInfo[]>;
|
|
29
29
|
export declare function serializeWorktreeInfo(info: WorktreeInfo): SerializedWorktreeInfo;
|
package/dist/worktree-info.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { readBranchLastCommitTimestamp, readWorktreeHealth } from
|
|
1
|
+
import { readBranchLastCommitTimestamp, readWorktreeHealth, } from "./git.js";
|
|
2
2
|
export async function readWorktreeInfos(worktrees) {
|
|
3
3
|
return Promise.all(worktrees.map((worktree) => readWorktreeInfo(worktree)));
|
|
4
4
|
}
|
|
5
5
|
async function readWorktreeInfo(worktree) {
|
|
6
6
|
const [healthResult, lastCommitResult] = await Promise.allSettled([
|
|
7
7
|
readWorktreeHealth(worktree.path),
|
|
8
|
-
worktree.branch === null
|
|
8
|
+
worktree.branch === null
|
|
9
|
+
? null
|
|
10
|
+
: readBranchLastCommitTimestamp(worktree.path, worktree.branch),
|
|
9
11
|
]);
|
|
10
|
-
const health = healthResult.status ===
|
|
11
|
-
const lastCommitTimestamp = lastCommitResult.status ===
|
|
12
|
-
? lastCommitResult.value
|
|
13
|
-
: null;
|
|
12
|
+
const health = healthResult.status === "fulfilled" ? healthResult.value : null;
|
|
13
|
+
const lastCommitTimestamp = lastCommitResult.status === "fulfilled" ? lastCommitResult.value : null;
|
|
14
14
|
return {
|
|
15
15
|
...worktree,
|
|
16
16
|
lastCommitTimestamp,
|
|
17
|
-
status: health?.status ??
|
|
17
|
+
status: health?.status ?? "unknown",
|
|
18
18
|
upstream: buildUpstreamState(worktree.branch, health),
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
function buildUpstreamState(branch, health) {
|
|
22
22
|
if (branch === null) {
|
|
23
|
-
return { kind:
|
|
23
|
+
return { kind: "detached" };
|
|
24
24
|
}
|
|
25
25
|
if (health === null) {
|
|
26
|
-
return { kind:
|
|
26
|
+
return { kind: "unknown" };
|
|
27
27
|
}
|
|
28
28
|
if (!health.hasUpstream) {
|
|
29
|
-
return { kind:
|
|
29
|
+
return { kind: "no-upstream" };
|
|
30
30
|
}
|
|
31
31
|
if (health.upstreamGone) {
|
|
32
|
-
return { kind:
|
|
32
|
+
return { kind: "stale" };
|
|
33
33
|
}
|
|
34
34
|
return {
|
|
35
35
|
ahead: health.ahead,
|
|
36
36
|
behind: health.behind,
|
|
37
|
-
kind:
|
|
37
|
+
kind: "tracked",
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
export function serializeWorktreeInfo(info) {
|
|
@@ -54,26 +54,26 @@ export function formatWorktreeHint(info) {
|
|
|
54
54
|
if (info.lastCommitTimestamp !== null) {
|
|
55
55
|
details.push(`last: ${formatRelativeAge(info.lastCommitTimestamp)}`);
|
|
56
56
|
}
|
|
57
|
-
return `${info.path} (${details.join(
|
|
57
|
+
return `${info.path} (${details.join(", ")})`;
|
|
58
58
|
}
|
|
59
59
|
export function formatUpstreamState(upstream) {
|
|
60
|
-
if (upstream.kind ===
|
|
61
|
-
return
|
|
60
|
+
if (upstream.kind === "detached") {
|
|
61
|
+
return "n/a";
|
|
62
62
|
}
|
|
63
|
-
if (upstream.kind ===
|
|
64
|
-
return
|
|
63
|
+
if (upstream.kind === "no-upstream") {
|
|
64
|
+
return "no-upstream";
|
|
65
65
|
}
|
|
66
|
-
if (upstream.kind ===
|
|
67
|
-
return
|
|
66
|
+
if (upstream.kind === "stale") {
|
|
67
|
+
return "gone";
|
|
68
68
|
}
|
|
69
|
-
if (upstream.kind ===
|
|
70
|
-
return
|
|
69
|
+
if (upstream.kind === "unknown") {
|
|
70
|
+
return "unknown";
|
|
71
71
|
}
|
|
72
72
|
return formatAheadBehind(upstream.ahead, upstream.behind);
|
|
73
73
|
}
|
|
74
74
|
function formatAheadBehind(ahead, behind) {
|
|
75
75
|
if (ahead === 0 && behind === 0) {
|
|
76
|
-
return
|
|
76
|
+
return "up to date";
|
|
77
77
|
}
|
|
78
78
|
if (ahead === 0) {
|
|
79
79
|
return `behind ${behind}`;
|
|
@@ -84,16 +84,18 @@ function formatAheadBehind(ahead, behind) {
|
|
|
84
84
|
return `ahead ${ahead}, behind ${behind}`;
|
|
85
85
|
}
|
|
86
86
|
export function formatLastCommit(timestampSeconds) {
|
|
87
|
-
return timestampSeconds === null
|
|
87
|
+
return timestampSeconds === null
|
|
88
|
+
? "n/a"
|
|
89
|
+
: formatRelativeAge(timestampSeconds);
|
|
88
90
|
}
|
|
89
91
|
export function formatRelativeAge(timestampSeconds) {
|
|
90
92
|
const ageSeconds = Math.max(0, Math.floor(Date.now() / 1000) - timestampSeconds);
|
|
91
93
|
const units = [
|
|
92
|
-
{ label:
|
|
93
|
-
{ label:
|
|
94
|
-
{ label:
|
|
95
|
-
{ label:
|
|
96
|
-
{ label:
|
|
94
|
+
{ label: "y", seconds: 365 * 24 * 60 * 60 },
|
|
95
|
+
{ label: "mo", seconds: 30 * 24 * 60 * 60 },
|
|
96
|
+
{ label: "d", seconds: 24 * 60 * 60 },
|
|
97
|
+
{ label: "h", seconds: 60 * 60 },
|
|
98
|
+
{ label: "m", seconds: 60 },
|
|
97
99
|
];
|
|
98
100
|
for (const unit of units) {
|
|
99
101
|
const value = Math.floor(ageSeconds / unit.seconds);
|
|
@@ -101,5 +103,5 @@ export function formatRelativeAge(timestampSeconds) {
|
|
|
101
103
|
return `${value}${unit.label} ago`;
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
|
-
return
|
|
106
|
+
return "just now";
|
|
105
107
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { execFile } from
|
|
2
|
-
import { promisify } from
|
|
3
|
-
import { detectRepository, listWorktrees } from
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { detectRepository, listWorktrees, } from "./repo.js";
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
5
|
// Force English output so error message string matching is locale-independent.
|
|
6
|
-
const GIT_ENV = { ...process.env, LC_ALL:
|
|
6
|
+
const GIT_ENV = { ...process.env, LC_ALL: "C" };
|
|
7
7
|
export async function loadLinkedWorktrees(cwd) {
|
|
8
8
|
const repository = await detectRepository(cwd);
|
|
9
9
|
const linkedWorktrees = (await listWorktrees(cwd)).filter((worktree) => worktree.path !== repository.repoRoot);
|
|
@@ -13,23 +13,38 @@ export async function loadLinkedWorktrees(cwd) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
export async function removeWorktree(repoRoot, worktreePath) {
|
|
16
|
-
await execFileAsync(
|
|
16
|
+
await execFileAsync("git", ["worktree", "remove", worktreePath], {
|
|
17
|
+
cwd: repoRoot,
|
|
18
|
+
env: GIT_ENV,
|
|
19
|
+
});
|
|
17
20
|
}
|
|
18
21
|
export async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
19
|
-
await execFileAsync(
|
|
22
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
|
|
23
|
+
cwd: repoRoot,
|
|
24
|
+
env: GIT_ENV,
|
|
25
|
+
});
|
|
20
26
|
}
|
|
21
27
|
export async function deleteBranch(repoRoot, branch) {
|
|
22
|
-
await execFileAsync(
|
|
28
|
+
await execFileAsync("git", ["branch", "-d", branch], {
|
|
29
|
+
cwd: repoRoot,
|
|
30
|
+
env: GIT_ENV,
|
|
31
|
+
});
|
|
23
32
|
}
|
|
24
33
|
export async function forceDeleteBranch(repoRoot, branch) {
|
|
25
|
-
await execFileAsync(
|
|
34
|
+
await execFileAsync("git", ["branch", "-D", branch], {
|
|
35
|
+
cwd: repoRoot,
|
|
36
|
+
env: GIT_ENV,
|
|
37
|
+
});
|
|
26
38
|
}
|
|
27
39
|
export function isWorktreeDirtyError(error) {
|
|
28
|
-
return hasStderr(error) &&
|
|
40
|
+
return (hasStderr(error) &&
|
|
41
|
+
error.stderr.includes("contains modified or untracked files"));
|
|
29
42
|
}
|
|
30
43
|
export function isBranchUnmergedError(error) {
|
|
31
|
-
return hasStderr(error) && error.stderr.includes(
|
|
44
|
+
return hasStderr(error) && error.stderr.includes("is not fully merged");
|
|
32
45
|
}
|
|
33
46
|
function hasStderr(error) {
|
|
34
|
-
return error instanceof Error &&
|
|
47
|
+
return (error instanceof Error &&
|
|
48
|
+
"stderr" in error &&
|
|
49
|
+
typeof error.stderr === "string");
|
|
35
50
|
}
|
package/dist/worktree-prompts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { confirm, isCancel } from
|
|
1
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
2
2
|
export async function defaultConfirmForceRemoveWorktree(worktreePath) {
|
|
3
3
|
const choice = await confirm({
|
|
4
|
-
active:
|
|
5
|
-
inactive:
|
|
4
|
+
active: "Yes",
|
|
5
|
+
inactive: "No",
|
|
6
6
|
initialValue: false,
|
|
7
7
|
message: `Worktree at ${worktreePath} has untracked or modified files. Force remove?`,
|
|
8
8
|
});
|
|
@@ -10,8 +10,8 @@ export async function defaultConfirmForceRemoveWorktree(worktreePath) {
|
|
|
10
10
|
}
|
|
11
11
|
export async function defaultConfirmForceDeleteBranch(branch) {
|
|
12
12
|
const choice = await confirm({
|
|
13
|
-
active:
|
|
14
|
-
inactive:
|
|
13
|
+
active: "Yes",
|
|
14
|
+
inactive: "No",
|
|
15
15
|
initialValue: false,
|
|
16
16
|
message: `Branch ${branch} has unmerged commits. Force delete?`,
|
|
17
17
|
});
|
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
package/man/man1/gji-config.1
CHANGED
package/man/man1/gji-go.1
CHANGED
package/man/man1/gji-history.1
CHANGED
package/man/man1/gji-init.1
CHANGED
package/man/man1/gji-ls.1
CHANGED
package/man/man1/gji-new.1
CHANGED
package/man/man1/gji-open.1
CHANGED
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-remove.1
CHANGED
package/man/man1/gji-root.1
CHANGED
package/man/man1/gji-status.1
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.TH GJI\-SYNC\-FILES 1 "May 2026" "gji 0.7.0" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
gji\-sync\-files \- manage local files copied into new worktrees
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B gji sync\-files [\fIoptions\fR] [options] [command]
|
|
6
|
+
.SH DESCRIPTION
|
|
7
|
+
manage local files copied into new worktrees
|
|
8
|
+
.SH OPTIONS
|
|
9
|
+
.TP
|
|
10
|
+
.B \-\-json
|
|
11
|
+
emit JSON instead of human\-readable output
|
|
12
|
+
.SH SUBCOMMANDS
|
|
13
|
+
.TP
|
|
14
|
+
.B list
|
|
15
|
+
list files synced into new worktrees for this repo
|
|
16
|
+
.TP
|
|
17
|
+
.B add
|
|
18
|
+
add repo\-local sync files to global config
|
|
19
|
+
.TP
|
|
20
|
+
.B remove
|
|
21
|
+
remove repo\-local sync files from global config
|
|
22
|
+
.SH "SEE ALSO"
|
|
23
|
+
.BR gji (1)
|
package/man/man1/gji-sync.1
CHANGED
package/man/man1/gji-warp.1
CHANGED
package/man/man1/gji.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI 1 "May 2026" "gji 0.
|
|
1
|
+
.TH GJI 1 "May 2026" "gji 0.7.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji \- Context switching without the mess.
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -46,6 +46,9 @@ summarize repository and worktree health
|
|
|
46
46
|
.B sync [options]
|
|
47
47
|
fetch and update one or all worktrees
|
|
48
48
|
.TP
|
|
49
|
+
.B sync\-files [options] [command]
|
|
50
|
+
manage local files copied into new worktrees
|
|
51
|
+
.TP
|
|
49
52
|
.B ls [options]
|
|
50
53
|
list active worktrees
|
|
51
54
|
.TP
|
|
@@ -81,6 +84,7 @@ output the version number
|
|
|
81
84
|
.BR gji\-root (1),
|
|
82
85
|
.BR gji\-status (1),
|
|
83
86
|
.BR gji\-sync (1),
|
|
87
|
+
.BR gji\-sync\-files (1),
|
|
84
88
|
.BR gji\-ls (1),
|
|
85
89
|
.BR gji\-clean (1),
|
|
86
90
|
.BR gji\-remove (1),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solaqua/gji",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Git worktree CLI for fast context switching.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "sjquant",
|
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
"docs:start": "pnpm --dir website start",
|
|
50
50
|
"prepublishOnly": "pnpm test && pnpm build && pnpm generate-man",
|
|
51
51
|
"test": "vitest run --passWithNoTests",
|
|
52
|
-
"test:watch": "vitest"
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"lint": "biome lint --write .",
|
|
54
|
+
"format": "biome format --write .",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"prepare": "prek install || true"
|
|
53
57
|
},
|
|
54
58
|
"dependencies": {
|
|
55
59
|
"@clack/core": "0.5.0",
|
|
@@ -58,6 +62,8 @@
|
|
|
58
62
|
"update-notifier": "^7.3.1"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
65
|
+
"@biomejs/biome": "^2.4.15",
|
|
66
|
+
"@j178/prek": "^0.3.13",
|
|
61
67
|
"@types/node": "^24.6.0",
|
|
62
68
|
"esbuild": "^0.27.0",
|
|
63
69
|
"typescript": "^5.9.3",
|