@solaqua/gji 0.11.0 → 0.12.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/README.md +82 -496
- package/dist/bootstrap-output.js +0 -1
- package/dist/clean.js +2 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +40 -4
- package/dist/context-card.d.ts +1 -0
- package/dist/context-card.js +25 -0
- package/dist/dependency-bootstrap.d.ts +10 -17
- package/dist/dependency-bootstrap.js +343 -108
- package/dist/dir-clone.d.ts +1 -2
- package/dist/dir-clone.js +303 -167
- package/dist/doctor.js +21 -0
- package/dist/done.js +2 -0
- package/dist/gji-bundle.mjs +1854 -1032
- package/dist/go.d.ts +1 -0
- package/dist/go.js +8 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +7 -1
- package/dist/hub.d.ts +31 -0
- package/dist/hub.js +299 -0
- package/dist/ls.js +9 -1
- package/dist/new.d.ts +1 -0
- package/dist/new.js +9 -0
- package/dist/pr-open.js +2 -10
- package/dist/pull-requests.d.ts +1 -0
- package/dist/pull-requests.js +32 -3
- package/dist/remove.js +2 -0
- package/dist/repo.d.ts +1 -0
- package/dist/repo.js +4 -0
- package/dist/run-hook.js +2 -0
- package/dist/safe-destination.js +10 -0
- package/dist/shell-completion.js +11 -3
- package/dist/slots.d.ts +8 -0
- package/dist/slots.js +61 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.js +9 -1
- package/dist/sync-directories.d.ts +0 -4
- package/dist/sync-directories.js +6 -26
- package/dist/task-command.d.ts +9 -0
- package/dist/task-command.js +29 -0
- package/dist/task.d.ts +9 -0
- package/dist/task.js +37 -0
- package/dist/terminal-text.d.ts +4 -0
- package/dist/terminal-text.js +72 -0
- package/dist/worktree-bootstrap.js +3 -2
- package/dist/worktree-info.d.ts +6 -1
- package/dist/worktree-info.js +14 -4
- package/dist/worktree-picker.d.ts +3 -3
- package/dist/worktree-picker.js +84 -82
- package/dist/worktree-sources.d.ts +1 -0
- package/dist/worktree-sources.js +57 -11
- 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-doctor.1 +1 -1
- package/man/man1/gji-done.1 +1 -1
- package/man/man1/gji-go.1 +4 -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 +4 -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-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +16 -0
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +1 -1
- package/dist/clone-failure-store.d.ts +0 -18
- package/dist/clone-failure-store.js +0 -230
package/dist/worktree-picker.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { env, platform, stdin, stdout } from "node:process";
|
|
2
2
|
import { isCancel, Prompt } from "@clack/core";
|
|
3
|
+
import { spinner } from "@clack/prompts";
|
|
3
4
|
import { loadHistory } from "./history.js";
|
|
4
5
|
import { createPullRequestQuery, } from "./pull-requests.js";
|
|
6
|
+
import { readTask } from "./task.js";
|
|
7
|
+
import { middleEllipsize, sanitizeTerminalText, startEllipsize, terminalWidth, } from "./terminal-text.js";
|
|
5
8
|
import { readWorktreeInfos, } from "./worktree-info.js";
|
|
6
9
|
const MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
|
|
10
|
+
const MAX_TASK_READ_CONCURRENCY = 8;
|
|
7
11
|
export async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
12
|
+
const loading = stdin.isTTY === true && stdout.isTTY === true
|
|
13
|
+
? spinner({ indicator: "timer" })
|
|
14
|
+
: null;
|
|
15
|
+
loading?.start("Loading worktrees");
|
|
16
|
+
try {
|
|
17
|
+
return await buildWorktreePromptEntriesWithoutLoading(sources, dependencies);
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
loading?.stop();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function buildWorktreePromptEntriesWithoutLoading(sources, dependencies) {
|
|
8
24
|
const metadataMode = dependencies.metadata ?? "full";
|
|
9
25
|
const includeMetadata = metadataMode === "full";
|
|
10
26
|
const pullRequestQuery = createPullRequestQuery();
|
|
@@ -22,7 +38,7 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
|
22
38
|
loadHistory(),
|
|
23
39
|
includeMetadata
|
|
24
40
|
? readWorktreeInfos(sources.map((source) => source.worktree))
|
|
25
|
-
:
|
|
41
|
+
: mapWithConcurrency(sources, MAX_TASK_READ_CONCURRENCY, (source) => createUnhydratedWorktreeInfo(source.worktree)),
|
|
26
42
|
repositoryPullRequests,
|
|
27
43
|
]);
|
|
28
44
|
const pullRequests = includeMetadata
|
|
@@ -34,11 +50,20 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
|
34
50
|
.sort(comparePromptEntries)
|
|
35
51
|
.map(({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry);
|
|
36
52
|
}
|
|
37
|
-
function createUnhydratedWorktreeInfo(worktree) {
|
|
53
|
+
async function createUnhydratedWorktreeInfo(worktree) {
|
|
54
|
+
let task = null;
|
|
55
|
+
try {
|
|
56
|
+
task = (await readTask(worktree.path))?.task ?? null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Task metadata is optional picker decoration.
|
|
60
|
+
}
|
|
38
61
|
return {
|
|
39
62
|
...worktree,
|
|
40
63
|
lastCommitTimestamp: null,
|
|
64
|
+
slot: null,
|
|
41
65
|
status: "unknown",
|
|
66
|
+
task,
|
|
42
67
|
upstream: { kind: "unknown" },
|
|
43
68
|
};
|
|
44
69
|
}
|
|
@@ -148,17 +173,24 @@ function buildSearchableWorktreeEntry(worktree) {
|
|
|
148
173
|
const metadata = worktree.metadata ?? null;
|
|
149
174
|
const branch = worktree.branch ?? "(detached)";
|
|
150
175
|
return {
|
|
151
|
-
detail: [
|
|
176
|
+
detail: [
|
|
177
|
+
worktree.repoName,
|
|
178
|
+
branch,
|
|
179
|
+
metadata,
|
|
180
|
+
worktree.task == null ? null : sanitizeTerminalText(worktree.task),
|
|
181
|
+
worktree.path,
|
|
182
|
+
]
|
|
152
183
|
.filter((part) => part !== null && part.length > 0)
|
|
153
184
|
.join(" · "),
|
|
154
185
|
label: worktree.label,
|
|
155
186
|
searchText: buildPromptSearchText(worktree),
|
|
156
187
|
value: worktree.path,
|
|
157
188
|
worktree: {
|
|
158
|
-
branch: formatWorktreeBranchLabel(worktree.branch, worktree.
|
|
189
|
+
branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequests),
|
|
159
190
|
metadata,
|
|
160
191
|
path: worktree.path,
|
|
161
192
|
repoName: worktree.repoName,
|
|
193
|
+
task: worktree.task == null ? null : sanitizeTerminalText(worktree.task),
|
|
162
194
|
},
|
|
163
195
|
};
|
|
164
196
|
}
|
|
@@ -488,6 +520,7 @@ class SearchablePrompt {
|
|
|
488
520
|
ellipsize: middleEllipsize,
|
|
489
521
|
max: 18,
|
|
490
522
|
min: 6,
|
|
523
|
+
removablePriority: entry.worktree.task === null ? undefined : 1,
|
|
491
524
|
value: entry.worktree.repoName,
|
|
492
525
|
},
|
|
493
526
|
{
|
|
@@ -503,13 +536,25 @@ class SearchablePrompt {
|
|
|
503
536
|
ellipsize: middleEllipsize,
|
|
504
537
|
max: 30,
|
|
505
538
|
min: 10,
|
|
539
|
+
removablePriority: entry.worktree.task === null ? undefined : 0,
|
|
506
540
|
value: entry.worktree.metadata,
|
|
507
541
|
},
|
|
508
542
|
]),
|
|
543
|
+
...(entry.worktree.task === null
|
|
544
|
+
? []
|
|
545
|
+
: [
|
|
546
|
+
{
|
|
547
|
+
ellipsize: middleEllipsize,
|
|
548
|
+
max: 36,
|
|
549
|
+
min: 10,
|
|
550
|
+
value: entry.worktree.task,
|
|
551
|
+
},
|
|
552
|
+
]),
|
|
509
553
|
{
|
|
510
554
|
ellipsize: startEllipsize,
|
|
511
555
|
max: 36,
|
|
512
556
|
min: 12,
|
|
557
|
+
removablePriority: entry.worktree.task === null ? undefined : 2,
|
|
513
558
|
value: entry.worktree.path,
|
|
514
559
|
},
|
|
515
560
|
], width);
|
|
@@ -745,13 +790,25 @@ function windowPromptEntries(entries, cursor, maxItems) {
|
|
|
745
790
|
}
|
|
746
791
|
return window;
|
|
747
792
|
}
|
|
793
|
+
function findPromptPieceToRemove(pieces, fallbackIndex) {
|
|
794
|
+
let candidate = fallbackIndex;
|
|
795
|
+
let priority = Infinity;
|
|
796
|
+
for (let index = 0; index < pieces.length; index += 1) {
|
|
797
|
+
const piecePriority = pieces[index].removablePriority;
|
|
798
|
+
if (piecePriority !== undefined && piecePriority < priority) {
|
|
799
|
+
candidate = index;
|
|
800
|
+
priority = piecePriority;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return candidate;
|
|
804
|
+
}
|
|
748
805
|
function fitPromptPieces(pieces, width) {
|
|
749
806
|
const separator = " · ";
|
|
750
807
|
let visiblePieces = pieces.filter((piece) => piece.value.length > 0);
|
|
751
808
|
let available = width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
|
|
752
809
|
while (visiblePieces.length > 1 &&
|
|
753
810
|
available < minimumPieceLength(visiblePieces)) {
|
|
754
|
-
const removableIndex = visiblePieces.length === 4 ? 2 : visiblePieces.length - 2;
|
|
811
|
+
const removableIndex = findPromptPieceToRemove(visiblePieces, visiblePieces.length === 4 ? 2 : visiblePieces.length - 2);
|
|
755
812
|
visiblePieces = visiblePieces.filter((_, index) => index !== removableIndex);
|
|
756
813
|
available =
|
|
757
814
|
width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
|
|
@@ -819,9 +876,13 @@ function buildPromptSearchText(worktree) {
|
|
|
819
876
|
worktree.branch ?? "detached",
|
|
820
877
|
worktree.path,
|
|
821
878
|
worktree.label,
|
|
879
|
+
worktree.task ?? "",
|
|
822
880
|
`${worktree.repoName}/${worktree.branch ?? "detached"}`,
|
|
823
|
-
...(worktree.
|
|
824
|
-
|
|
881
|
+
...(worktree.pullRequests ?? []).flatMap((pullRequest) => [
|
|
882
|
+
`#${pullRequest.number}`,
|
|
883
|
+
pullRequest.title ?? "",
|
|
884
|
+
pullRequest.url,
|
|
885
|
+
]),
|
|
825
886
|
]
|
|
826
887
|
.join(" ")
|
|
827
888
|
.toLowerCase();
|
|
@@ -834,7 +895,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
834
895
|
: lastWorkedTimestamp !== null
|
|
835
896
|
? "worked"
|
|
836
897
|
: null;
|
|
837
|
-
const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests
|
|
898
|
+
const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests);
|
|
838
899
|
const badges = buildStatusBadges(info);
|
|
839
900
|
const recency = formatPromptRecency(lastActivityTimestamp, lastActivityType, now);
|
|
840
901
|
const status = badges.length > 0 ? badges.map((badge) => `[${badge}]`).join(" ") : null;
|
|
@@ -845,6 +906,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
845
906
|
middleEllipsize(source.repoName, 22),
|
|
846
907
|
middleEllipsize(branch, 34),
|
|
847
908
|
metadata,
|
|
909
|
+
info.task === null
|
|
910
|
+
? null
|
|
911
|
+
: middleEllipsize(sanitizeTerminalText(info.task), 36),
|
|
848
912
|
path,
|
|
849
913
|
]
|
|
850
914
|
.filter((part) => part !== null && part.length > 0)
|
|
@@ -855,9 +919,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
|
|
|
855
919
|
label,
|
|
856
920
|
lastActivityTimestamp,
|
|
857
921
|
metadata,
|
|
858
|
-
|
|
859
|
-
pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
|
|
922
|
+
pullRequests,
|
|
860
923
|
repoName: source.repoName,
|
|
924
|
+
task: info.task,
|
|
861
925
|
};
|
|
862
926
|
}
|
|
863
927
|
async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
|
|
@@ -915,11 +979,18 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
915
979
|
function sortSourcePullRequests(pullRequests) {
|
|
916
980
|
return [...(pullRequests ?? [])].sort((a, b) => a.number - b.number);
|
|
917
981
|
}
|
|
918
|
-
export function formatWorktreeBranchLabel(branch,
|
|
982
|
+
export function formatWorktreeBranchLabel(branch, pullRequests = []) {
|
|
919
983
|
const branchLabel = branch ?? "(detached)";
|
|
920
|
-
if (
|
|
984
|
+
if (pullRequests.length === 0)
|
|
921
985
|
return branchLabel;
|
|
922
|
-
return `${branchLabel} (${
|
|
986
|
+
return `${branchLabel} (${pullRequests
|
|
987
|
+
.map((pullRequest) => {
|
|
988
|
+
const title = pullRequest.title
|
|
989
|
+
? ` ${middleEllipsize(sanitizeTerminalText(pullRequest.title), 48)}`
|
|
990
|
+
: "";
|
|
991
|
+
return `#${pullRequest.number}${title}`;
|
|
992
|
+
})
|
|
993
|
+
.join(", ")})`;
|
|
923
994
|
}
|
|
924
995
|
function buildStatusBadges(info) {
|
|
925
996
|
const badges = [];
|
|
@@ -1023,72 +1094,3 @@ function scoreWorktreeMatch(entry, query) {
|
|
|
1023
1094
|
}
|
|
1024
1095
|
return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
|
|
1025
1096
|
}
|
|
1026
|
-
function middleEllipsize(value, maxLength) {
|
|
1027
|
-
if (terminalWidth(value) <= maxLength) {
|
|
1028
|
-
return value;
|
|
1029
|
-
}
|
|
1030
|
-
if (maxLength <= 1) {
|
|
1031
|
-
return "…";
|
|
1032
|
-
}
|
|
1033
|
-
const keep = maxLength - 1;
|
|
1034
|
-
const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
|
|
1035
|
-
const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
|
|
1036
|
-
return `${start}…${end}`;
|
|
1037
|
-
}
|
|
1038
|
-
function startEllipsize(value, maxLength) {
|
|
1039
|
-
if (terminalWidth(value) <= maxLength) {
|
|
1040
|
-
return value;
|
|
1041
|
-
}
|
|
1042
|
-
if (maxLength <= 1) {
|
|
1043
|
-
return "…";
|
|
1044
|
-
}
|
|
1045
|
-
return `…${takeTerminalColumns(value, maxLength - 1, "end")}`;
|
|
1046
|
-
}
|
|
1047
|
-
function takeTerminalColumns(value, maxWidth, direction) {
|
|
1048
|
-
const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
|
|
1049
|
-
const kept = [];
|
|
1050
|
-
let width = 0;
|
|
1051
|
-
for (const character of characters) {
|
|
1052
|
-
const characterWidth = terminalWidth(character);
|
|
1053
|
-
if (width + characterWidth > maxWidth)
|
|
1054
|
-
break;
|
|
1055
|
-
kept.push(character);
|
|
1056
|
-
width += characterWidth;
|
|
1057
|
-
}
|
|
1058
|
-
return direction === "start" ? kept.join("") : kept.reverse().join("");
|
|
1059
|
-
}
|
|
1060
|
-
function terminalWidth(value) {
|
|
1061
|
-
let width = 0;
|
|
1062
|
-
for (const character of Array.from(value)) {
|
|
1063
|
-
width += characterTerminalWidth(character);
|
|
1064
|
-
}
|
|
1065
|
-
return width;
|
|
1066
|
-
}
|
|
1067
|
-
function characterTerminalWidth(character) {
|
|
1068
|
-
const codePoint = character.codePointAt(0);
|
|
1069
|
-
if (codePoint === undefined)
|
|
1070
|
-
return 0;
|
|
1071
|
-
if (isZeroWidthCodePoint(codePoint) || /\p{Mark}/u.test(character)) {
|
|
1072
|
-
return 0;
|
|
1073
|
-
}
|
|
1074
|
-
return isWideCodePoint(codePoint) ? 2 : 1;
|
|
1075
|
-
}
|
|
1076
|
-
function isZeroWidthCodePoint(codePoint) {
|
|
1077
|
-
return (codePoint === 0 ||
|
|
1078
|
-
codePoint === 0x200d ||
|
|
1079
|
-
(codePoint >= 0xfe00 && codePoint <= 0xfe0f));
|
|
1080
|
-
}
|
|
1081
|
-
function isWideCodePoint(codePoint) {
|
|
1082
|
-
return ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
|
|
1083
|
-
codePoint === 0x2329 ||
|
|
1084
|
-
codePoint === 0x232a ||
|
|
1085
|
-
(codePoint >= 0x2e80 && codePoint <= 0xa4cf) ||
|
|
1086
|
-
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
1087
|
-
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
1088
|
-
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
1089
|
-
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
1090
|
-
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
1091
|
-
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
1092
|
-
(codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
|
|
1093
|
-
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
1094
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type RepoRegistryEntry } from "./repo-registry.js";
|
|
2
2
|
import type { WorktreeSource } from "./worktree-source.js";
|
|
3
3
|
export declare function listRegisteredWorktreeSources(cwd: string, onSkipped?: (entry: RepoRegistryEntry) => void): Promise<WorktreeSource[]>;
|
|
4
|
+
export declare function listDiscoverableWorktreeSources(cwd: string, onSkipped?: (entry: RepoRegistryEntry) => void): Promise<WorktreeSource[]>;
|
package/dist/worktree-sources.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import { detectRepository, listWorktrees } from "./repo.js";
|
|
2
2
|
import { loadRegistry } from "./repo-registry.js";
|
|
3
|
+
const MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
|
|
3
4
|
export async function listRegisteredWorktreeSources(cwd, onSkipped) {
|
|
4
5
|
const registry = await loadRegistry();
|
|
5
6
|
const currentRoot = await detectRepository(cwd)
|
|
6
7
|
.then((repository) => repository.currentRoot)
|
|
7
8
|
.catch(() => null);
|
|
8
|
-
const results = await
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const results = await mapWithConcurrency(registry, MAX_REPOSITORY_DISCOVERY_CONCURRENCY, async (entry) => {
|
|
10
|
+
try {
|
|
11
|
+
const worktrees = await listWorktrees(entry.path);
|
|
12
|
+
return { entry, worktrees };
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
onSkipped?.(entry);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
12
19
|
const allItems = [];
|
|
13
|
-
for (const
|
|
14
|
-
if (result
|
|
15
|
-
onSkipped?.(registry[index]);
|
|
20
|
+
for (const result of results) {
|
|
21
|
+
if (result === null)
|
|
16
22
|
continue;
|
|
17
|
-
}
|
|
18
|
-
const { repoName, repoRoot, worktrees } = result.value;
|
|
23
|
+
const { entry, worktrees } = result;
|
|
19
24
|
for (const worktree of worktrees) {
|
|
20
25
|
allItems.push({
|
|
21
|
-
repoRoot,
|
|
22
|
-
repoName,
|
|
26
|
+
repoRoot: entry.path,
|
|
27
|
+
repoName: entry.name,
|
|
23
28
|
worktree: {
|
|
24
29
|
...worktree,
|
|
25
30
|
isCurrent: currentRoot !== null && worktree.path === currentRoot,
|
|
@@ -29,3 +34,44 @@ export async function listRegisteredWorktreeSources(cwd, onSkipped) {
|
|
|
29
34
|
}
|
|
30
35
|
return allItems;
|
|
31
36
|
}
|
|
37
|
+
export async function listDiscoverableWorktreeSources(cwd, onSkipped) {
|
|
38
|
+
const currentRepository = await detectRepository(cwd).catch(() => null);
|
|
39
|
+
const registeredSources = await listRegisteredWorktreeSources(cwd, onSkipped);
|
|
40
|
+
if (currentRepository === null)
|
|
41
|
+
return dedupeSources(registeredSources);
|
|
42
|
+
let currentSources = [];
|
|
43
|
+
try {
|
|
44
|
+
currentSources = (await listWorktrees(cwd)).map((worktree) => ({
|
|
45
|
+
repoName: currentRepository.repoName,
|
|
46
|
+
repoRoot: currentRepository.repoRoot,
|
|
47
|
+
worktree,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Registered repositories remain discoverable when the current checkout is transiently unavailable.
|
|
52
|
+
}
|
|
53
|
+
return dedupeSources([...currentSources, ...registeredSources]);
|
|
54
|
+
}
|
|
55
|
+
async function mapWithConcurrency(items, limit, mapper) {
|
|
56
|
+
const results = new Array(items.length);
|
|
57
|
+
let nextIndex = 0;
|
|
58
|
+
async function readNext() {
|
|
59
|
+
for (;;) {
|
|
60
|
+
const index = nextIndex++;
|
|
61
|
+
if (index >= items.length)
|
|
62
|
+
return;
|
|
63
|
+
results[index] = await mapper(items[index]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => readNext()));
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
function dedupeSources(sources) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
return sources.filter((source) => {
|
|
72
|
+
if (seen.has(source.worktree.path))
|
|
73
|
+
return false;
|
|
74
|
+
seen.add(source.worktree.path);
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
}
|
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-doctor.1
CHANGED
package/man/man1/gji-done.1
CHANGED
package/man/man1/gji-go.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-GO 1 "
|
|
1
|
+
.TH GJI\-GO 1 "August 2026" "gji 0.12.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji\-go \- resolve and jump to a worktree path
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -13,6 +13,9 @@ navigate to the main repository root
|
|
|
13
13
|
.B \-\-print
|
|
14
14
|
print the resolved worktree path explicitly
|
|
15
15
|
.TP
|
|
16
|
+
.B \-\-quiet
|
|
17
|
+
suppress the context card
|
|
18
|
+
.TP
|
|
16
19
|
.B \-\-json
|
|
17
20
|
emit JSON for an existing worktree destination
|
|
18
21
|
.SH "SEE ALSO"
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-NEW 1 "
|
|
1
|
+
.TH GJI\-NEW 1 "August 2026" "gji 0.12.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji\-new \- create a new branch or detached linked worktree and CoW\-bootstrap configured directories
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -28,6 +28,9 @@ skip refreshing the remote default branch before creating the new branch
|
|
|
28
28
|
.B \-\-take
|
|
29
29
|
move current uncommitted changes into the new worktree
|
|
30
30
|
.TP
|
|
31
|
+
.B \-\-task <description>
|
|
32
|
+
record the purpose of the new worktree
|
|
33
|
+
.TP
|
|
31
34
|
.B \-\-copy
|
|
32
35
|
copy current uncommitted changes instead of moving them (requires \-\-take)
|
|
33
36
|
.TP
|
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-run-hook.1
CHANGED
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.TH GJI\-TASK 1 "August 2026" "gji 0.12.1" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
gji\-task \- show or update the current worktree task
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B gji task [\fIoptions\fR] [options] [task]
|
|
6
|
+
.SH DESCRIPTION
|
|
7
|
+
show or update the current worktree task
|
|
8
|
+
.SH OPTIONS
|
|
9
|
+
.TP
|
|
10
|
+
.B \-\-clear
|
|
11
|
+
clear the current worktree task
|
|
12
|
+
.TP
|
|
13
|
+
.B \-\-json
|
|
14
|
+
emit task metadata as JSON
|
|
15
|
+
.SH "SEE ALSO"
|
|
16
|
+
.BR gji (1)
|
package/man/man1/gji-undo.1
CHANGED
package/man/man1/gji-warp.1
CHANGED
package/man/man1/gji.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI 1 "
|
|
1
|
+
.TH GJI 1 "August 2026" "gji 0.12.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji \- Context switching without the mess.
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -52,6 +52,9 @@ print the main repository root path
|
|
|
52
52
|
.B status [options]
|
|
53
53
|
summarize repository and worktree health
|
|
54
54
|
.TP
|
|
55
|
+
.B task [options] [task]
|
|
56
|
+
show or update the current worktree task
|
|
57
|
+
.TP
|
|
55
58
|
.B sync [options]
|
|
56
59
|
fetch and update one or all worktrees
|
|
57
60
|
.TP
|
|
@@ -97,6 +100,7 @@ output the version number
|
|
|
97
100
|
.BR gji\-go (1),
|
|
98
101
|
.BR gji\-root (1),
|
|
99
102
|
.BR gji\-status (1),
|
|
103
|
+
.BR gji\-task (1),
|
|
100
104
|
.BR gji\-sync (1),
|
|
101
105
|
.BR gji\-sync\-files (1),
|
|
102
106
|
.BR gji\-ls (1),
|
package/package.json
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface CloneFailureStore {
|
|
2
|
-
isCached(repoRoot: string, directory: string, scope?: string): Promise<boolean>;
|
|
3
|
-
cache(repoRoot: string, directory: string, reason: string, scope?: string): Promise<void>;
|
|
4
|
-
clear(repoRoot: string, directory: string, scope?: string): Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
export declare class FileCloneFailureStore implements CloneFailureStore {
|
|
7
|
-
private updateQueue;
|
|
8
|
-
isCached(repoRoot: string, directory: string, scope?: string): Promise<boolean>;
|
|
9
|
-
cache(repoRoot: string, directory: string, reason: string, scope?: string): Promise<void>;
|
|
10
|
-
clear(repoRoot: string, directory: string, scope?: string): Promise<void>;
|
|
11
|
-
private update;
|
|
12
|
-
private withStateLock;
|
|
13
|
-
private readState;
|
|
14
|
-
private writeState;
|
|
15
|
-
private stateFilePath;
|
|
16
|
-
}
|
|
17
|
-
export declare const defaultCloneFailureStore: CloneFailureStore;
|
|
18
|
-
export declare function cloneFailureScope(source: string, destination: string): Promise<string>;
|