@n-seiji/nuthatch 0.1.5 → 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/README.ja.md +31 -21
- package/README.md +31 -21
- package/dist/clean-rn2476my.js +217 -0
- package/dist/cli-26d2drt6.js +30 -0
- package/dist/cli-77yanq37.js +21 -0
- package/dist/cli-9n04gsq1.js +108 -0
- package/dist/cli-a56b2ar0.js +20 -0
- package/dist/cli-b0tk5wcx.js +86 -0
- package/dist/cli-btqjpf1q.js +35 -0
- package/dist/cli-dbzx9nzg.js +40 -0
- package/dist/cli-fhta3tr8.js +126 -0
- package/dist/cli-hz0r7zw8.js +194 -0
- package/dist/cli-ph0gp56k.js +54 -0
- package/dist/cli-pick-yjd4qpd9.js +1444 -0
- package/dist/cli-s5qcjcwk.js +25 -0
- package/dist/cli-sre95k9x.js +87 -0
- package/dist/cli-update-x0r4b4xk.js +639 -0
- package/dist/cli-wcnyhk4w.js +124 -0
- package/dist/cli-y8vsqre6.js +97 -0
- package/dist/cli.js +245 -5868
- package/{shell/init.zsh → dist/init-167d1bbs.js} +22 -4
- package/dist/jump-1zkraat1.js +108 -0
- package/dist/ls-3hyjf60d.js +34 -0
- package/dist/render-ls-x4hpnsvy.js +149 -0
- package/dist/rm-eg3j8ae1.js +7 -0
- package/dist/root-49vcb8x2.js +7 -0
- package/package.json +4 -6
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REFS_HEADS_PREFIX
|
|
3
|
+
} from "./cli-b0tk5wcx.js";
|
|
4
|
+
|
|
5
|
+
// src/domain/upstream.ts
|
|
6
|
+
var NONE = { kind: "none" };
|
|
7
|
+
var FIELD_SEPARATOR = "\x00";
|
|
8
|
+
var UPSTREAM_FORMAT = "%(refname)%00%(upstream)%00%(upstream:track,nobracket)";
|
|
9
|
+
var countOf = (track, word) => {
|
|
10
|
+
const match = new RegExp(`${word} (?<count>\\d+)`, "u").exec(track);
|
|
11
|
+
return match?.groups?.count === undefined ? 0 : Number(match.groups.count);
|
|
12
|
+
};
|
|
13
|
+
var parseTrack = (upstream, track) => {
|
|
14
|
+
if (upstream.length === 0) {
|
|
15
|
+
return NONE;
|
|
16
|
+
}
|
|
17
|
+
if (track === "gone") {
|
|
18
|
+
return { kind: "gone" };
|
|
19
|
+
}
|
|
20
|
+
return { kind: "tracking", ahead: countOf(track, "ahead"), behind: countOf(track, "behind") };
|
|
21
|
+
};
|
|
22
|
+
var parseUpstreamStates = (output) => {
|
|
23
|
+
const states = new Map;
|
|
24
|
+
for (const line of output.split(`
|
|
25
|
+
`)) {
|
|
26
|
+
const [refname = "", upstream = "", track = ""] = line.split(FIELD_SEPARATOR);
|
|
27
|
+
if (refname.startsWith(REFS_HEADS_PREFIX)) {
|
|
28
|
+
states.set(refname.slice(REFS_HEADS_PREFIX.length), parseTrack(upstream, track.trim()));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return states;
|
|
32
|
+
};
|
|
33
|
+
var upstreamStateOf = (states, branch) => states.get(branch) ?? NONE;
|
|
34
|
+
|
|
35
|
+
export { UPSTREAM_FORMAT, parseUpstreamStates, upstreamStateOf };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/render.ts
|
|
2
|
+
var PLAIN_JSON_INDENT = 2;
|
|
3
|
+
var render = (command, result, json) => {
|
|
4
|
+
if (json) {
|
|
5
|
+
const envelope = {
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
command,
|
|
8
|
+
data: result.ok ? result.data : undefined,
|
|
9
|
+
warnings: result.warnings ?? []
|
|
10
|
+
};
|
|
11
|
+
process.stdout.write(`${JSON.stringify(envelope)}
|
|
12
|
+
`);
|
|
13
|
+
if (!result.ok && result.errorMessage !== undefined) {
|
|
14
|
+
process.stderr.write(`${result.errorMessage}
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (result.path !== undefined) {
|
|
20
|
+
process.stdout.write(`${result.path}
|
|
21
|
+
`);
|
|
22
|
+
} else if (result.ok && result.data !== undefined) {
|
|
23
|
+
process.stdout.write(`${JSON.stringify(result.data, null, PLAIN_JSON_INDENT)}
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
for (const warning of result.warnings ?? []) {
|
|
27
|
+
process.stderr.write(`warning: ${warning}
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
if (!result.ok && result.errorMessage !== undefined) {
|
|
31
|
+
process.stderr.write(`${result.errorMessage}
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var reportResult = (command, result, json) => {
|
|
36
|
+
render(command, result, json);
|
|
37
|
+
process.exitCode = result.exitCode;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { render, reportResult };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/domain/display-width.ts
|
|
2
|
+
var WIDTH_ZERO = 0;
|
|
3
|
+
var WIDTH_NARROW = 1;
|
|
4
|
+
var WIDTH_WIDE = 2;
|
|
5
|
+
var segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
6
|
+
var ZERO_WIDTH_JOINER = 8205;
|
|
7
|
+
var VARIATION_SELECTOR_TEXT = 65038;
|
|
8
|
+
var VARIATION_SELECTOR_EMOJI = 65039;
|
|
9
|
+
var ZERO_WIDTH_CODE_POINTS = new Set([
|
|
10
|
+
ZERO_WIDTH_JOINER,
|
|
11
|
+
VARIATION_SELECTOR_TEXT,
|
|
12
|
+
VARIATION_SELECTOR_EMOJI
|
|
13
|
+
]);
|
|
14
|
+
var isCombiningMark = (codePoint) => /\p{M}/u.test(String.fromCodePoint(codePoint));
|
|
15
|
+
var HANGUL_JAMO_START = 4352;
|
|
16
|
+
var HANGUL_JAMO_END = 4447;
|
|
17
|
+
var CJK_RADICALS_START = 11904;
|
|
18
|
+
var CJK_SYMBOLS_END = 12350;
|
|
19
|
+
var HIRAGANA_START = 12353;
|
|
20
|
+
var CJK_COMPATIBILITY_END = 13311;
|
|
21
|
+
var CJK_EXT_A_START = 13312;
|
|
22
|
+
var CJK_EXT_A_END = 19903;
|
|
23
|
+
var CJK_UNIFIED_START = 19968;
|
|
24
|
+
var CJK_UNIFIED_END = 40959;
|
|
25
|
+
var YI_SYLLABLES_START = 40960;
|
|
26
|
+
var YI_SYLLABLES_END = 42191;
|
|
27
|
+
var HANGUL_SYLLABLES_START = 44032;
|
|
28
|
+
var HANGUL_SYLLABLES_END = 55203;
|
|
29
|
+
var CJK_COMPAT_IDEOGRAPHS_START = 63744;
|
|
30
|
+
var CJK_COMPAT_IDEOGRAPHS_END = 64255;
|
|
31
|
+
var CJK_COMPAT_FORMS_START = 65072;
|
|
32
|
+
var CJK_COMPAT_FORMS_END = 65103;
|
|
33
|
+
var FULLWIDTH_FORMS_START = 65280;
|
|
34
|
+
var FULLWIDTH_FORMS_END = 65376;
|
|
35
|
+
var FULLWIDTH_SIGNS_START = 65504;
|
|
36
|
+
var FULLWIDTH_SIGNS_END = 65510;
|
|
37
|
+
var EMOJI_MISC_START = 127744;
|
|
38
|
+
var EMOJI_MISC_END = 128591;
|
|
39
|
+
var EMOJI_TRANSPORT_START = 128640;
|
|
40
|
+
var EMOJI_TRANSPORT_END = 129535;
|
|
41
|
+
var CJK_EXT_B_START = 131072;
|
|
42
|
+
var CJK_EXT_B_END = 262141;
|
|
43
|
+
var WIDE_RANGES = [
|
|
44
|
+
[HANGUL_JAMO_START, HANGUL_JAMO_END],
|
|
45
|
+
[CJK_RADICALS_START, CJK_SYMBOLS_END],
|
|
46
|
+
[HIRAGANA_START, CJK_COMPATIBILITY_END],
|
|
47
|
+
[CJK_EXT_A_START, CJK_EXT_A_END],
|
|
48
|
+
[CJK_UNIFIED_START, CJK_UNIFIED_END],
|
|
49
|
+
[YI_SYLLABLES_START, YI_SYLLABLES_END],
|
|
50
|
+
[HANGUL_SYLLABLES_START, HANGUL_SYLLABLES_END],
|
|
51
|
+
[CJK_COMPAT_IDEOGRAPHS_START, CJK_COMPAT_IDEOGRAPHS_END],
|
|
52
|
+
[CJK_COMPAT_FORMS_START, CJK_COMPAT_FORMS_END],
|
|
53
|
+
[FULLWIDTH_FORMS_START, FULLWIDTH_FORMS_END],
|
|
54
|
+
[FULLWIDTH_SIGNS_START, FULLWIDTH_SIGNS_END],
|
|
55
|
+
[EMOJI_MISC_START, EMOJI_MISC_END],
|
|
56
|
+
[EMOJI_TRANSPORT_START, EMOJI_TRANSPORT_END],
|
|
57
|
+
[CJK_EXT_B_START, CJK_EXT_B_END]
|
|
58
|
+
];
|
|
59
|
+
var isWide = (codePoint) => WIDE_RANGES.some(([start, end]) => codePoint >= start && codePoint <= end);
|
|
60
|
+
var codePointWidth = (codePoint) => {
|
|
61
|
+
if (ZERO_WIDTH_CODE_POINTS.has(codePoint) || isCombiningMark(codePoint)) {
|
|
62
|
+
return WIDTH_ZERO;
|
|
63
|
+
}
|
|
64
|
+
return isWide(codePoint) ? WIDTH_WIDE : WIDTH_NARROW;
|
|
65
|
+
};
|
|
66
|
+
var graphemeWidth = (grapheme) => {
|
|
67
|
+
const codePoints = Array.from(grapheme, (char) => char.codePointAt(0)).filter((codePoint) => codePoint !== undefined);
|
|
68
|
+
return codePoints.reduce((max, codePoint) => Math.max(max, codePointWidth(codePoint)), WIDTH_ZERO);
|
|
69
|
+
};
|
|
70
|
+
var graphemes = (value) => Array.from(segmenter.segment(value), (entry) => entry.segment);
|
|
71
|
+
var displayWidth = (value) => graphemes(value).reduce((total, grapheme) => total + graphemeWidth(grapheme), WIDTH_ZERO);
|
|
72
|
+
var padToWidth = (value, width) => {
|
|
73
|
+
const current = displayWidth(value);
|
|
74
|
+
return current >= width ? value : value + " ".repeat(width - current);
|
|
75
|
+
};
|
|
76
|
+
var DEFAULT_ELLIPSIS = "…";
|
|
77
|
+
var takeWithinWidth = (clusters, budget) => {
|
|
78
|
+
const taken = [];
|
|
79
|
+
let usedWidth = WIDTH_ZERO;
|
|
80
|
+
for (const grapheme of clusters) {
|
|
81
|
+
const nextWidth = usedWidth + graphemeWidth(grapheme);
|
|
82
|
+
if (nextWidth > budget) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
taken.push(grapheme);
|
|
86
|
+
usedWidth = nextWidth;
|
|
87
|
+
}
|
|
88
|
+
return taken;
|
|
89
|
+
};
|
|
90
|
+
var truncateToWidth = (value, width, ellipsis = DEFAULT_ELLIPSIS) => {
|
|
91
|
+
if (displayWidth(value) <= width) {
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
const budget = width - displayWidth(ellipsis);
|
|
95
|
+
if (budget < WIDTH_ZERO) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
return takeWithinWidth(graphemes(value), budget).join("") + ellipsis;
|
|
99
|
+
};
|
|
100
|
+
var truncateToWidthKeepingTail = (value, width, ellipsis = DEFAULT_ELLIPSIS) => {
|
|
101
|
+
if (displayWidth(value) <= width) {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
const budget = width - displayWidth(ellipsis);
|
|
105
|
+
if (budget < WIDTH_ZERO) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
const keptFromEnd = takeWithinWidth(graphemes(value).toReversed(), budget);
|
|
109
|
+
return ellipsis + keptFromEnd.toReversed().join("");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/domain/ansi.ts
|
|
113
|
+
var ESC = "\x1B";
|
|
114
|
+
var SGR = {
|
|
115
|
+
bold: `${ESC}[1m`,
|
|
116
|
+
dim: `${ESC}[2m`,
|
|
117
|
+
inverse: `${ESC}[7m`,
|
|
118
|
+
red: `${ESC}[31m`,
|
|
119
|
+
green: `${ESC}[32m`,
|
|
120
|
+
yellow: `${ESC}[33m`,
|
|
121
|
+
magenta: `${ESC}[35m`,
|
|
122
|
+
cyan: `${ESC}[36m`
|
|
123
|
+
};
|
|
124
|
+
var SGR_RESET = `${ESC}[0m`;
|
|
125
|
+
|
|
126
|
+
export { graphemes, displayWidth, padToWidth, truncateToWidth, truncateToWidthKeepingTail, ESC, SGR, SGR_RESET };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
messageOf
|
|
3
|
+
} from "./cli-ph0gp56k.js";
|
|
4
|
+
import {
|
|
5
|
+
withRepoLock
|
|
6
|
+
} from "./cli-wcnyhk4w.js";
|
|
7
|
+
import {
|
|
8
|
+
refArgumentRejection,
|
|
9
|
+
loadRepoContext,
|
|
10
|
+
isWorktreeDirty,
|
|
11
|
+
resolveBranchCheckout
|
|
12
|
+
} from "./cli-y8vsqre6.js";
|
|
13
|
+
import {
|
|
14
|
+
EXIT_SAFE_REJECTION,
|
|
15
|
+
ok,
|
|
16
|
+
fail,
|
|
17
|
+
pass,
|
|
18
|
+
reject
|
|
19
|
+
} from "./cli-77yanq37.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/root-holder-swap.ts
|
|
22
|
+
var holderRejection = (branch, holderPath, reason) => fail(EXIT_SAFE_REJECTION, `Branch "${branch}" is already checked out at ${holderPath}, and it ${reason}. Not swapping — cd there instead of using hop root.`);
|
|
23
|
+
var prunableHolderRejection = (branch, holderPath) => fail(EXIT_SAFE_REJECTION, `Branch "${branch}" is held by a stale worktree registration at ${holderPath} (git reports it prunable). Not swapping — if that worktree was moved, re-link it with "git worktree repair <new path>"; otherwise drop the registration first ("hop rm ${branch}" or "git worktree prune").`);
|
|
24
|
+
var multipleHoldersRejection = (branch, holders) => fail(EXIT_SAFE_REJECTION, `Branch "${branch}" is checked out at ${holders.length} worktrees at once (${holders.map((holder) => holder.path).join(", ")}). Not swapping — hop won't guess which one to detach.`);
|
|
25
|
+
var resolveHolderSwap = async ({
|
|
26
|
+
git,
|
|
27
|
+
fresh,
|
|
28
|
+
target,
|
|
29
|
+
switchOptions,
|
|
30
|
+
policy
|
|
31
|
+
}) => {
|
|
32
|
+
if (target === "-") {
|
|
33
|
+
return pass(null);
|
|
34
|
+
}
|
|
35
|
+
const freshHolders = fresh.worktrees.filter((wt) => wt.branch === target && wt.kind !== "root");
|
|
36
|
+
if (freshHolders.length > 1) {
|
|
37
|
+
return reject(multipleHoldersRejection(target, freshHolders));
|
|
38
|
+
}
|
|
39
|
+
const [freshHolder] = freshHolders;
|
|
40
|
+
if (freshHolder === undefined) {
|
|
41
|
+
return pass(null);
|
|
42
|
+
}
|
|
43
|
+
if (policy.expectedPath !== undefined && freshHolder.path !== policy.expectedPath) {
|
|
44
|
+
return reject(holderRejection(target, freshHolder.path, `changed from the picker selection at ${policy.expectedPath}`));
|
|
45
|
+
}
|
|
46
|
+
if (freshHolder.kind === "external" && !policy.allowExternal) {
|
|
47
|
+
return reject(holderRejection(target, freshHolder.path, "is external and requires confirmation before it can be put into detached HEAD"));
|
|
48
|
+
}
|
|
49
|
+
if (switchOptions.createBranch === true) {
|
|
50
|
+
return reject(holderRejection(target, freshHolder.path, "wasn't visible when hop checked existing local branches (e.g. it has no commits yet, or was created after that check)"));
|
|
51
|
+
}
|
|
52
|
+
if (freshHolder.locked) {
|
|
53
|
+
return reject(holderRejection(target, freshHolder.path, `is locked by git${freshHolder.lockReason === null ? "" : ` (${freshHolder.lockReason})`}`));
|
|
54
|
+
}
|
|
55
|
+
if (freshHolder.prunable) {
|
|
56
|
+
return reject(prunableHolderRejection(target, freshHolder.path));
|
|
57
|
+
}
|
|
58
|
+
const holderDirty = await isWorktreeDirty(git, fresh.worktrees, freshHolder.path);
|
|
59
|
+
if (holderDirty) {
|
|
60
|
+
return reject(holderRejection(target, freshHolder.path, "has uncommitted or untracked changes"));
|
|
61
|
+
}
|
|
62
|
+
await git.detachHead(freshHolder.path);
|
|
63
|
+
return pass({ path: freshHolder.path, branch: target });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/commands/root.ts
|
|
67
|
+
var root2 = async (git, fs, options) => {
|
|
68
|
+
if (options.target !== undefined && options.target !== "-") {
|
|
69
|
+
const refRejection = refArgumentRejection(options.target, options.track);
|
|
70
|
+
if (refRejection !== null) {
|
|
71
|
+
return refRejection;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const context = await loadRepoContext(git, fs, options.cwd);
|
|
75
|
+
if (options.target === undefined) {
|
|
76
|
+
return ok({
|
|
77
|
+
path: context.rootPath,
|
|
78
|
+
data: {
|
|
79
|
+
branch: rootBranchOf(context),
|
|
80
|
+
switched: false,
|
|
81
|
+
detachedHolder: null
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const dirtyRejection = await dirtyRootRejection(git, context);
|
|
86
|
+
if (dirtyRejection !== null) {
|
|
87
|
+
return dirtyRejection;
|
|
88
|
+
}
|
|
89
|
+
if (options.target === "-") {
|
|
90
|
+
return switchAndReport({
|
|
91
|
+
git,
|
|
92
|
+
fs,
|
|
93
|
+
context,
|
|
94
|
+
target: "-",
|
|
95
|
+
switchOptions: {},
|
|
96
|
+
holderPolicy: { allowExternal: true }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const checkout = await resolveBranchCheckout(git, context.rootPath, options.target, options.track);
|
|
100
|
+
if (!checkout.ok) {
|
|
101
|
+
return checkout.rejection;
|
|
102
|
+
}
|
|
103
|
+
return switchAndReport({
|
|
104
|
+
git,
|
|
105
|
+
fs,
|
|
106
|
+
context,
|
|
107
|
+
target: options.target,
|
|
108
|
+
switchOptions: checkout.value,
|
|
109
|
+
holderPolicy: {
|
|
110
|
+
allowExternal: options.allowExternalHolderSwap ?? true,
|
|
111
|
+
...options.expectedHolderPath === undefined ? {} : { expectedPath: options.expectedHolderPath }
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
var rootBranchOf = ({ worktrees }) => worktrees.find((wt) => wt.kind === "root")?.branch ?? null;
|
|
116
|
+
var dirtyRootRejection = async (git, context) => {
|
|
117
|
+
const dirty = await isWorktreeDirty(git, context.worktrees, context.rootPath);
|
|
118
|
+
if (!dirty) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return fail(EXIT_SAFE_REJECTION, "Root clone has uncommitted or untracked changes. Commit, stash, or discard them before switching.");
|
|
122
|
+
};
|
|
123
|
+
var rollBackSwitch = async (git, { rootPath, previousBranch, detachedHolder }) => {
|
|
124
|
+
const warnings = [];
|
|
125
|
+
if (previousBranch !== null) {
|
|
126
|
+
try {
|
|
127
|
+
await git.switchBranch(rootPath, previousBranch, {});
|
|
128
|
+
} catch {
|
|
129
|
+
warnings.push(`Could not restore ${rootPath} to its original branch (${previousBranch})`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (detachedHolder !== null) {
|
|
133
|
+
try {
|
|
134
|
+
await git.switchBranch(detachedHolder.path, detachedHolder.branch, {});
|
|
135
|
+
} catch {
|
|
136
|
+
warnings.push(`Could not restore ${detachedHolder.path} to its original branch (${detachedHolder.branch}); it remains in detached HEAD`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return warnings;
|
|
140
|
+
};
|
|
141
|
+
var switchAndReport = ({
|
|
142
|
+
git,
|
|
143
|
+
fs,
|
|
144
|
+
context,
|
|
145
|
+
target,
|
|
146
|
+
switchOptions,
|
|
147
|
+
holderPolicy
|
|
148
|
+
}) => withRepoLock(context.commonDir, async () => {
|
|
149
|
+
let detachedHolder = null;
|
|
150
|
+
let previousBranch = null;
|
|
151
|
+
try {
|
|
152
|
+
const fresh = await loadRepoContext(git, fs, context.rootPath);
|
|
153
|
+
previousBranch = rootBranchOf(fresh);
|
|
154
|
+
const stillDirty = await dirtyRootRejection(git, fresh);
|
|
155
|
+
if (stillDirty !== null) {
|
|
156
|
+
return stillDirty;
|
|
157
|
+
}
|
|
158
|
+
const holderSwap = await resolveHolderSwap({
|
|
159
|
+
git,
|
|
160
|
+
fresh,
|
|
161
|
+
target,
|
|
162
|
+
switchOptions,
|
|
163
|
+
policy: holderPolicy
|
|
164
|
+
});
|
|
165
|
+
if (!holderSwap.ok) {
|
|
166
|
+
return holderSwap.rejection;
|
|
167
|
+
}
|
|
168
|
+
detachedHolder = holderSwap.value;
|
|
169
|
+
await git.switchBranch(fresh.rootPath, target, switchOptions);
|
|
170
|
+
let resolvedBranch = target;
|
|
171
|
+
if (target === "-") {
|
|
172
|
+
const afterSwitch = await loadRepoContext(git, fs, fresh.rootPath);
|
|
173
|
+
resolvedBranch = rootBranchOf(afterSwitch);
|
|
174
|
+
}
|
|
175
|
+
return ok({
|
|
176
|
+
path: fresh.rootPath,
|
|
177
|
+
data: {
|
|
178
|
+
branch: resolvedBranch,
|
|
179
|
+
switched: true,
|
|
180
|
+
detachedHolder: detachedHolder?.path ?? null
|
|
181
|
+
},
|
|
182
|
+
warnings: detachedHolder === null ? [] : [`Put ${detachedHolder.path} into detached HEAD`]
|
|
183
|
+
});
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const warnings = await rollBackSwitch(git, {
|
|
186
|
+
rootPath: context.rootPath,
|
|
187
|
+
previousBranch,
|
|
188
|
+
detachedHolder
|
|
189
|
+
});
|
|
190
|
+
return fail(EXIT_SAFE_REJECTION, `Failed to switch root: ${messageOf(error)}`, warnings);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export { root2 };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXIT_GENERAL_ERROR,
|
|
3
|
+
EXIT_SAFE_REJECTION
|
|
4
|
+
} from "./cli-77yanq37.js";
|
|
5
|
+
import {
|
|
6
|
+
absoluteCandidates,
|
|
7
|
+
splitPathEnv
|
|
8
|
+
} from "./cli-s5qcjcwk.js";
|
|
9
|
+
|
|
10
|
+
// src/domain/git-executable.ts
|
|
11
|
+
var GIT_EXECUTABLE_OVERRIDE_ENV = "HOP_GIT";
|
|
12
|
+
var GIT_NOT_FOUND_ERROR_CODE = "HOP_GIT_NOT_FOUND";
|
|
13
|
+
var GIT_NOT_FOUND_HINT = `Install git, or set ${GIT_EXECUTABLE_OVERRIDE_ENV} to its absolute path.`;
|
|
14
|
+
var GIT_FALLBACK_BIN_DIRS = [
|
|
15
|
+
"/opt/homebrew/bin",
|
|
16
|
+
"/usr/local/bin",
|
|
17
|
+
"/usr/bin",
|
|
18
|
+
"/bin"
|
|
19
|
+
];
|
|
20
|
+
var gitExecutableCandidates = (input) => {
|
|
21
|
+
const override = input.override?.trim() ?? "";
|
|
22
|
+
if (override.length > 0) {
|
|
23
|
+
return [override];
|
|
24
|
+
}
|
|
25
|
+
return absoluteCandidates([...splitPathEnv(input.path), ...GIT_FALLBACK_BIN_DIRS], "git");
|
|
26
|
+
};
|
|
27
|
+
var gitNotFoundMessage = (candidates) => `git executable not found. Looked in: ${candidates.join(", ")}. ${GIT_NOT_FOUND_HINT}`;
|
|
28
|
+
|
|
29
|
+
// src/domain/fatal-error.ts
|
|
30
|
+
var fieldsOf = (error) => typeof error === "object" && error !== null ? error : {};
|
|
31
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
32
|
+
var trimmedStringOf = (value) => typeof value === "string" ? value.trim() : "";
|
|
33
|
+
var isSpawnFailure = (fields) => fields.code === "ENOENT" && typeof fields.syscall === "string" && fields.syscall.startsWith("spawn");
|
|
34
|
+
var isGitFailure = (fields) => typeof fields.code === "number" || typeof fields.signal === "string" && fields.signal !== "";
|
|
35
|
+
var describeFatalError = (error) => {
|
|
36
|
+
const fields = fieldsOf(error);
|
|
37
|
+
if (isSpawnFailure(fields)) {
|
|
38
|
+
const binary = trimmedStringOf(fields.path) || "git";
|
|
39
|
+
return {
|
|
40
|
+
message: `failed to run ${binary}: no such executable. ${GIT_NOT_FOUND_HINT}`,
|
|
41
|
+
exitCode: EXIT_GENERAL_ERROR
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (isGitFailure(fields)) {
|
|
45
|
+
const stderr = trimmedStringOf(fields.stderr);
|
|
46
|
+
return {
|
|
47
|
+
message: stderr.length > 0 ? stderr : messageOf(error),
|
|
48
|
+
exitCode: EXIT_SAFE_REJECTION
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { message: messageOf(error), exitCode: EXIT_GENERAL_ERROR };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { GIT_EXECUTABLE_OVERRIDE_ENV, GIT_NOT_FOUND_ERROR_CODE, gitExecutableCandidates, gitNotFoundMessage, messageOf, describeFatalError };
|