@descent-vtt/spec-harness 0.20.0 → 0.21.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +113 -0
- package/README.md +13 -7
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +119 -30
- package/dist/cli.js.map +1 -1
- package/dist/configure.d.ts +40 -3
- package/dist/configure.js +75 -9
- package/dist/configure.js.map +1 -1
- package/dist/fs.d.ts +30 -4
- package/dist/fs.js +104 -11
- package/dist/fs.js.map +1 -1
- package/dist/git.d.ts +38 -0
- package/dist/git.js +59 -2
- package/dist/git.js.map +1 -1
- package/dist/guard.d.ts +20 -1
- package/dist/guard.js +31 -0
- package/dist/guard.js.map +1 -1
- package/dist/hooks.d.ts +14 -0
- package/dist/hooks.js +14 -0
- package/dist/hooks.js.map +1 -1
- package/dist/round.d.ts +19 -1
- package/dist/round.js +59 -16
- package/dist/round.js.map +1 -1
- package/dist/server.js +42 -7
- package/dist/server.js.map +1 -1
- package/dist/siblings.d.ts +5 -3
- package/dist/siblings.js +52 -31
- package/dist/siblings.js.map +1 -1
- package/dist/workspace.js +23 -13
- package/dist/workspace.js.map +1 -1
- package/package.json +3 -3
package/dist/fs.d.ts
CHANGED
|
@@ -54,6 +54,31 @@ export declare class StateError extends Error {
|
|
|
54
54
|
* `src/db/schema.ts` on Windows or macOS, where both name one file.
|
|
55
55
|
*/
|
|
56
56
|
export declare function realSpelling(path: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* The real spelling of the file a write of `given` reaches, read from `cwd`
|
|
59
|
+
* where it is relative; `null` where it reaches none, through more links
|
|
60
|
+
* than the system follows or a loop of them. Linux reads a `..` after the
|
|
61
|
+
* link before it, and macOS is read so, so `deep/../schema.ts`, `deep` a
|
|
62
|
+
* link to `lib/x`, is written as `lib/schema.ts` (measured on Linux), where
|
|
63
|
+
* {@link realSpelling} reads the `..` first and names `schema.ts`; Windows
|
|
64
|
+
* reads the `..` first, as resolve does (measured, through a junction). And
|
|
65
|
+
* a link to nothing is followed, since a write opens the file it names and
|
|
66
|
+
* makes it there (measured on Linux and on GitHub's Windows runner), where
|
|
67
|
+
* `realSpelling` names the link.
|
|
68
|
+
* The names are walked one by one from the root, each link replaced by what
|
|
69
|
+
* it names, so the directory reached holds no link, and `join` reads a `.`,
|
|
70
|
+
* a `..` and an empty name from it as the system does; what is not there
|
|
71
|
+
* yet is made as written.
|
|
72
|
+
*/
|
|
73
|
+
export declare function writtenSpelling(given: string, cwd: string): string | null;
|
|
74
|
+
/**
|
|
75
|
+
* Every file a write of `given` may reach, each by its real spelling: the
|
|
76
|
+
* one the path reads as ({@link realSpelling}), and the one the system
|
|
77
|
+
* writes ({@link writtenSpelling}) where that is another. The hook does not
|
|
78
|
+
* know which of the two a tool writes, so it decides both (ADR-0005). A
|
|
79
|
+
* path that reaches no file is read as it reads.
|
|
80
|
+
*/
|
|
81
|
+
export declare function reachedBy(given: string, cwd: string): string[];
|
|
57
82
|
/**
|
|
58
83
|
* Whether `directory` is the one git names as the top of a work tree: the
|
|
59
84
|
* two spelled alike once the filesystem has spelled the directory, links
|
|
@@ -91,10 +116,11 @@ export declare function repositoryPath(given: string, root: string, cwd: string)
|
|
|
91
116
|
export declare function workTrees(top: string, common: string): string[] | null;
|
|
92
117
|
/**
|
|
93
118
|
* Whether every path lies outside every work tree of the repository whose
|
|
94
|
-
* top and common directory are given
|
|
95
|
-
*
|
|
96
|
-
* brief
|
|
97
|
-
*
|
|
119
|
+
* top and common directory are given, as it reads and as the system writes
|
|
120
|
+
* it ({@link reachedBy}): such a path is no file of the repository's, so no
|
|
121
|
+
* brief of its decides it, whatever is wrong with the brief or the
|
|
122
|
+
* configuration (ADR-0005). Where the work trees cannot all be named, a
|
|
123
|
+
* path is not known to be outside them, and this is `false`.
|
|
98
124
|
*/
|
|
99
125
|
export declare function outsideEvery(paths: readonly string[], top: string, common: string, cwd: string): boolean;
|
|
100
126
|
/**
|
package/dist/fs.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* The edge where the harness meets the disk: the configuration, the state
|
|
3
3
|
* directory outside the work tree, and paths as the filesystem spells them.
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
5
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync } from 'node:fs';
|
|
6
6
|
import { lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig } from './config.js';
|
|
9
9
|
/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing. */
|
|
10
10
|
function standing(path) {
|
|
@@ -140,6 +140,94 @@ export function realSpelling(path) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The most links the written reading of a path follows. Linux follows at
|
|
145
|
+
* most 40 in resolving a pathname, and fails the call with ELOOP past them
|
|
146
|
+
* (path_resolution(7)), so no write reaches a file through more.
|
|
147
|
+
*/
|
|
148
|
+
const MOST_LINKS = 40;
|
|
149
|
+
/**
|
|
150
|
+
* A path's names, separated as the system separates them: Windows takes `/`
|
|
151
|
+
* as well as `\`, where Linux and macOS take a `\` for a character of a name.
|
|
152
|
+
* The sweep runs on Linux, where the mutants that read the pattern there, or
|
|
153
|
+
* change it, are equivalent; the suite's Windows leg holds them.
|
|
154
|
+
*/
|
|
155
|
+
function namesOf(path) {
|
|
156
|
+
return path.split(sep === '/' ? '/' : /[\\/]/);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The real spelling of the file a write of `given` reaches, read from `cwd`
|
|
160
|
+
* where it is relative; `null` where it reaches none, through more links
|
|
161
|
+
* than the system follows or a loop of them. Linux reads a `..` after the
|
|
162
|
+
* link before it, and macOS is read so, so `deep/../schema.ts`, `deep` a
|
|
163
|
+
* link to `lib/x`, is written as `lib/schema.ts` (measured on Linux), where
|
|
164
|
+
* {@link realSpelling} reads the `..` first and names `schema.ts`; Windows
|
|
165
|
+
* reads the `..` first, as resolve does (measured, through a junction). And
|
|
166
|
+
* a link to nothing is followed, since a write opens the file it names and
|
|
167
|
+
* makes it there (measured on Linux and on GitHub's Windows runner), where
|
|
168
|
+
* `realSpelling` names the link.
|
|
169
|
+
* The names are walked one by one from the root, each link replaced by what
|
|
170
|
+
* it names, so the directory reached holds no link, and `join` reads a `.`,
|
|
171
|
+
* a `..` and an empty name from it as the system does; what is not there
|
|
172
|
+
* yet is made as written.
|
|
173
|
+
*/
|
|
174
|
+
export function writtenSpelling(given, cwd) {
|
|
175
|
+
// On Linux, where the sweep runs, the mutant that reads every path as
|
|
176
|
+
// Windows does is caught, and the one that reads every path as Linux does
|
|
177
|
+
// is equivalent; the suite's Windows leg holds it. The root is taken off
|
|
178
|
+
// the names as it is off a link's, below.
|
|
179
|
+
const absolute = sep === '/' ? (isAbsolute(given) ? given : `${cwd}/${given}`) : resolve(cwd, given);
|
|
180
|
+
const root = parse(absolute).root;
|
|
181
|
+
const names = namesOf(absolute.slice(root.length));
|
|
182
|
+
let at = root;
|
|
183
|
+
let links = 0;
|
|
184
|
+
while (names.length > 0) {
|
|
185
|
+
const next = join(at, names.shift());
|
|
186
|
+
let target;
|
|
187
|
+
try {
|
|
188
|
+
if (!lstatSync(next).isSymbolicLink()) {
|
|
189
|
+
at = next;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
target = readlinkSync(next);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Nothing there, or nothing the account may look into: a write makes
|
|
196
|
+
// what is not there as written, and reaches nothing past a directory
|
|
197
|
+
// it may not look into.
|
|
198
|
+
at = join(next, ...names);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
links += 1;
|
|
202
|
+
if (links > MOST_LINKS)
|
|
203
|
+
return null;
|
|
204
|
+
// A link names a path from the directory it stands in, or from a root,
|
|
205
|
+
// which is taken off its names: on Linux and macOS the root is `/`,
|
|
206
|
+
// whose name is empty and read as none, so keeping it there is
|
|
207
|
+
// equivalent; on Windows it is the drive (a test holds it there).
|
|
208
|
+
const top = parse(target).root;
|
|
209
|
+
if (top !== '')
|
|
210
|
+
at = top;
|
|
211
|
+
names.unshift(...namesOf(target.slice(top.length)));
|
|
212
|
+
}
|
|
213
|
+
// The walk spells what exists as given, links followed; where case is
|
|
214
|
+
// ignored, the entries' case is the real one. On Linux, where the sweep
|
|
215
|
+
// runs, the two are one, so the mutant that leaves this out is equivalent
|
|
216
|
+
// there (a test holds it on Windows and macOS).
|
|
217
|
+
return realSpelling(at);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Every file a write of `given` may reach, each by its real spelling: the
|
|
221
|
+
* one the path reads as ({@link realSpelling}), and the one the system
|
|
222
|
+
* writes ({@link writtenSpelling}) where that is another. The hook does not
|
|
223
|
+
* know which of the two a tool writes, so it decides both (ADR-0005). A
|
|
224
|
+
* path that reaches no file is read as it reads.
|
|
225
|
+
*/
|
|
226
|
+
export function reachedBy(given, cwd) {
|
|
227
|
+
const read = realSpelling(resolve(cwd, given));
|
|
228
|
+
const written = writtenSpelling(given, cwd);
|
|
229
|
+
return written === null || written === read ? [read] : [read, written];
|
|
230
|
+
}
|
|
143
231
|
/**
|
|
144
232
|
* Whether `directory` is the one git names as the top of a work tree: the
|
|
145
233
|
* two spelled alike once the filesystem has spelled the directory, links
|
|
@@ -213,10 +301,11 @@ export function workTrees(top, common) {
|
|
|
213
301
|
}
|
|
214
302
|
/**
|
|
215
303
|
* Whether every path lies outside every work tree of the repository whose
|
|
216
|
-
* top and common directory are given
|
|
217
|
-
*
|
|
218
|
-
* brief
|
|
219
|
-
*
|
|
304
|
+
* top and common directory are given, as it reads and as the system writes
|
|
305
|
+
* it ({@link reachedBy}): such a path is no file of the repository's, so no
|
|
306
|
+
* brief of its decides it, whatever is wrong with the brief or the
|
|
307
|
+
* configuration (ADR-0005). Where the work trees cannot all be named, a
|
|
308
|
+
* path is not known to be outside them, and this is `false`.
|
|
220
309
|
*/
|
|
221
310
|
export function outsideEvery(paths, top, common, cwd) {
|
|
222
311
|
// A write that names no path is no write outside: no caller asks of
|
|
@@ -228,14 +317,18 @@ export function outsideEvery(paths, top, common, cwd) {
|
|
|
228
317
|
// among the work trees, so the mutant without this test is equivalent.
|
|
229
318
|
if (paths.some((path) => repositoryPath(path, top, cwd) !== null))
|
|
230
319
|
return false;
|
|
231
|
-
// A path
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
320
|
+
// A path with a `..` in it was held here when the hook read a path one
|
|
321
|
+
// way, its `..` before its links: `/elsewhere/link/../a.ts`, the link to
|
|
322
|
+
// a directory of the work tree, read as outside and was written inside.
|
|
323
|
+
// Both readings are read below, and such a path is held as it was all
|
|
324
|
+
// the same (ADR-0005).
|
|
235
325
|
if (paths.some((path) => path.split(/[\\/]/).includes('..')))
|
|
236
326
|
return false;
|
|
327
|
+
// As a path reads and as the system writes it, a link to nothing among
|
|
328
|
+
// its links: the file it reaches is outside too, or the path is not.
|
|
329
|
+
const reached = paths.flatMap((path) => reachedBy(path, cwd));
|
|
237
330
|
const trees = workTrees(top, common);
|
|
238
|
-
return trees !== null &&
|
|
331
|
+
return trees !== null && reached.every((path) => trees.every((tree) => repositoryPath(path, tree, cwd) === null));
|
|
239
332
|
}
|
|
240
333
|
/**
|
|
241
334
|
* The harness's own directory under the git common directory, created on
|
package/dist/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync, type Stats } from 'node:fs';\nimport { lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport type { BriefInputs } from './briefs.js';\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing. */\nfunction standing(path: string): Stats | null {\n try {\n return statSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a regular file stands at a path, links followed. Only one is read\n * where a file is expected: opened, a named pipe nothing writes to is read\n * without end, and one read as the process waits on it, as the harness reads\n * a few small files, stops every timer with it (ADR-0002).\n */\nexport function isFile(path: string): boolean {\n return standing(path)?.isFile() === true;\n}\n\n/**\n * Reads `.spec-harness.json` at the root, or the defaults when there is none.\n *\n * Only a file is read. A named pipe under the file's name was read without\n * end, by every command and by the hook before every write; and Node's words\n * for a directory, EISDIR, are not the harness's. A file that cannot be read\n * is said as that, with the reason: one the account may not read, or one\n * another process holds open on Windows, was said to be JSON that is not\n * valid, which sent a person to its text. What does not load is thrown with\n * the path it stands at, which the hook lets a write reach (ADR-0005).\n */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n const there = standing(file);\n if (there === null) return { config: DEFAULT_CONFIG, file: null };\n if (!there.isFile()) throw new ConfigError(`${CONFIG_FILE} is ${there.isDirectory() ? 'a directory' : 'neither a file nor a directory'}, not a configuration file`, file);\n let text: string;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n text = await readFile(file, 'utf8');\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} cannot be read: ${(error as Error).message}`, file);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`, file);\n }\n try {\n return { config: parseConfig(raw), file };\n } catch (error) {\n // parseConfig throws nothing but its own refusal, whose words are kept.\n throw new ConfigError((error as Error).message, file);\n }\n}\n\n/**\n * The real path of a file that stands at `path` under that name alone: a\n * file, not a link to one, and with no other name, since a write to a file\n * with two names writes both. `null` for anything else, and for nothing.\n */\nfunction soleFile(path: string): string | null {\n try {\n const entry = lstatSync(path);\n return entry.isFile() && entry.nlink === 1 ? realpathSync.native(path) : null;\n } catch {\n // Nothing at the path, or nothing the account may look at. Its callers\n // compare the answer with a real path, which neither `null` nor what an\n // empty block answers is, so that mutant is equivalent.\n return null;\n }\n}\n\n/**\n * Whether every path of a write names `file` and nothing else: a file that\n * stands under that name alone, which the path names as the hook resolves a\n * path to decide it ({@link realSpelling}), and as the filesystem resolves\n * it to write, which on Linux and macOS follows a link before a `..` where\n * the hook reads the `..` first. A path that only reads like it, a link to\n * it, a second name of it, and a file of another work tree are none of\n * these. `cwd` is what a relative path is read from, its words kept as\n * written.\n */\nexport function writesOnly(paths: readonly string[], file: string, cwd: string): boolean {\n // Where the file is none, no path's real spelling is `null`, so no path\n // names it.\n const own = soleFile(file);\n return paths.every((given) => {\n const path = isAbsolute(given) ? given : `${cwd}${sep}${given}`;\n return soleFile(path) === own && realSpelling(path) === own;\n });\n}\n\n/**\n * A path where the harness keeps its state that holds something else: said\n * in a line, exit 2, where Node's ENOTDIR and a stack sent a person looking\n * for a defect (spec-core ADR-0005).\n */\nexport class StateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StateError';\n }\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * Whether `directory` is the one git names as the top of a work tree: the\n * two spelled alike once the filesystem has spelled the directory, links\n * and short names resolved, as git spells a top. A spelling that differs is\n * read as another directory: what git said from the one is then not taken\n * for what it would say from the other.\n */\nexport function isTop(directory: string, top: string): boolean {\n return realSpelling(directory).split(sep).join('/') === top;\n}\n\n/**\n * The repository's main work tree, where git keeps the repository as that\n * work tree's `.git`, as `git init` and `git clone` do and every linked\n * worktree's common directory then is: `null` for a common directory of any\n * other name, a bare repository's, a submodule's under its superproject's\n * `modules`, or one kept apart with `--separate-git-dir`. Read from the\n * name, with no process: it says only where an install is looked for.\n */\nexport function mainWorkTree(commonDir: string): string | null {\n return basename(commonDir) === '.git' ? dirname(commonDir) : null;\n}\n\n/** Whether a `.git` stands in `directory`, a repository's or the file that names one, as at the top of a work tree. */\nexport function holdsGit(directory: string): boolean {\n return existsSync(join(directory, '.git'));\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/**\n * The top of every work tree of the repository whose common directory is\n * `common`, `top` among them, read with no process: the main work tree,\n * whose `.git` the common directory is, and each linked worktree, by the\n * `gitdir` file git keeps for it under the common directory's `worktrees`,\n * which names the worktree's `.git` (gitrepository-layout(5)), absolute or\n * from its own directory. `null` where they cannot all be named: a common\n * directory of any other name, whose main work tree only its configuration\n * names, and an entry under `worktrees` that cannot be read.\n */\nexport function workTrees(top: string, common: string): string[] | null {\n const main = mainWorkTree(common);\n if (main === null) return null;\n const kept = join(common, 'worktrees');\n let names: string[] = [];\n try {\n names = readdirSync(kept);\n } catch (error) {\n // No such directory is no linked worktree, and anything else there is\n // one that cannot be read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return null;\n }\n const linked = names.map((name) => textOf(join(kept, name, 'gitdir')));\n if (linked.includes(null)) return null;\n // The line's end stands in the last name, the worktree's `.git`, which\n // dirname takes away with it.\n return [top, main, ...names.map((name, at) => dirname(resolve(kept, name, linked[at]!)))];\n}\n\n/**\n * Whether every path lies outside every work tree of the repository whose\n * top and common directory are given: such a path is no file of the\n * repository's, so no brief of its decides it, whatever is wrong with the\n * brief or the configuration (ADR-0005). Where the work trees cannot all\n * be named, a path is not known to be outside them, and this is `false`.\n */\nexport function outsideEvery(paths: readonly string[], top: string, common: string, cwd: string): boolean {\n // A write that names no path is no write outside: no caller asks of\n // one, and a test holds the answer.\n if (paths.length === 0) return false;\n // A path in the work tree that was opened, as nearly every write's is,\n // is told with nothing of git's directory read; read anyway, `top` is\n // among the work trees, so the mutant without this test is equivalent.\n if (paths.some((path) => repositoryPath(path, top, cwd) !== null)) return false;\n // A path is read here as the hook reads one, its `..` before its links,\n // where Linux and macOS write it after: `/elsewhere/link/../a.ts`, the\n // link to a directory of the work tree, reads as outside and is written\n // inside. Such a path is not known to be outside.\n if (paths.some((path) => path.split(/[\\\\/]/).includes('..'))) return false;\n const trees = workTrees(top, common);\n return trees !== null && paths.every((path) => trees.every((tree) => repositoryPath(path, tree, cwd) === null));\n}\n\n/**\n * The harness's own directory under the git common directory, created on\n * demand (ADR-0003). Where something that is no directory stands at its path,\n * or at one between it and git's, Node refuses to make it, with ENOTDIR or\n * EEXIST, and the harness says which path holds what.\n */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n try {\n await mkdir(directory, { recursive: true });\n } catch (error) {\n const { code } = error as NodeJS.ErrnoException;\n if (code !== 'ENOTDIR' && code !== 'EEXIST') throw error;\n // mkdir refuses for this alone, so one of the steps is there and is no\n // directory, and every step before it is one.\n const steps = ['spec-harness', ...parts].map((_, at, all) => join(commonDir, ...all.slice(0, at + 1)));\n const there = steps.find((step) => !standing(step)!.isDirectory())!;\n throw new StateError(`${there} is not a directory, where spec-harness keeps its state in one: move it away, and run the command again`);\n }\n return directory;\n}\n\n/**\n * A work tree and its git directories as the files git keeps name them, read\n * with no process: the nearest directory at or above `root` that holds a\n * `.git`; the git directory, that `.git` or the one a `.git` file names\n * (`gitdir: <path>`, relative to the file's directory); and the common\n * directory, the one that git directory's `commondir` file names, relative\n * to it, or the git directory itself (gitrepository-layout(5)). `exact` is\n * whether git, asked, would name the same work tree: not where\n * `GIT_DIR` or `GIT_WORK_TREE` sends it elsewhere, and not where the\n * repository's configuration names a `worktree`, which `core.worktree`\n * moves it to (git-config(1)), or keeps one of its own for a worktree.\n * `null` where no directory up to the top of the filesystem holds one, or\n * a `.git` file names none.\n */\nexport function gitDirectories(root: string, env: Readonly<Record<string, string | undefined>>): { readonly top: string; readonly git: string; readonly common: string; readonly exact: boolean } | null {\n let top = realSpelling(root);\n let found: Stats | null = null;\n for (;;) {\n found = standing(join(top, '.git'));\n if (found !== null) break;\n const parent = dirname(top);\n if (parent === top) return null;\n top = parent;\n }\n const dotGit = join(top, '.git');\n let gitDir = dotGit;\n if (found.isFile()) {\n // The path is the rest of the line: `.` stops at a line's end, a\n // carriage return among them, so the mutant that drops `$` is\n // equivalent; and a Buffer is read as its text, so is the encoding's.\n const named = /^gitdir: (.+)$/m.exec(readFileSync(dotGit, 'utf8'))?.[1];\n if (named === undefined) return null;\n gitDir = resolve(top, named);\n }\n const commondir = textOf(join(gitDir, 'commondir'));\n const common = commondir === null ? gitDir : resolve(gitDir, commondir.trim());\n const moved = env['GIT_DIR'] !== undefined || env['GIT_WORK_TREE'] !== undefined;\n // No configuration holds no key, as a text without one does: the mutant\n // of the empty text is equivalent.\n const elsewhere = /^\\s*worktree\\s*=/im.test(textOf(join(common, 'config')) ?? '') || existsSync(join(gitDir, 'config.worktree'));\n return { top, git: gitDir, common, exact: !moved && !elsewhere };\n}\n\n/** A file's text, or `null` where it cannot be read or is no file. */\nfunction textOf(file: string): string | null {\n if (!isFile(file)) return null;\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return null;\n }\n}\n\n/**\n * How long a call's decisions are kept for a run after the write that does\n * not come: a call refused by the person, one that failed, one stopped\n * (ADR-0002). A day, since a call waits on a person's prompt for as long as\n * the person is away, and an entry removed too soon costs only that call's\n * answer being decided again.\n */\nexport const CALL_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps what a run before a write decided, under the common directory, for\n * the run after it, and removes what no run after a write read in a day.\n * Nothing here may change the answer before the write: whatever fails is\n * passed over, and the run after the write then decides as it did.\n */\nexport async function keepCall(commonDir: string, name: string, text: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'calls');\n await writeAtomic(join(directory, name), text);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > CALL_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the call is decided again after the write.\n }\n}\n\n/** What was kept for a call, taken away as it is read; `null` where nothing was. */\nexport async function takeCall(commonDir: string, name: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'calls', name);\n // What is no file holds nothing kept, and is written over at the next write.\n if (!isFile(file)) return null;\n try {\n const text = await readFile(file, 'utf8');\n // Taken away by another run since it was read, the mutants of `force`\n // decide the call again, which answers as the text read does: they are\n // equivalent.\n await rm(file, { force: true });\n return text;\n } catch {\n return null;\n }\n}\n\n/** SHA-256 of a text or of bytes, in hex. */\nexport type Hash = (data: string | Uint8Array) => string;\n\n/** SHA-256, loaded where something is hashed: a run that keeps no answer loads no hash (ADR-0002). */\nexport async function hasher(): Promise<Hash> {\n const { createHash } = await import('node:crypto');\n return (data) => createHash('sha256').update(data).digest('hex');\n}\n\n/**\n * How long a session's answer of spec-brief's is kept once it was last\n * kept (ADR-0003). A day, as a call's: an answer removed costs one start of\n * spec-brief, and one kept is a few kilobytes.\n */\nexport const ANSWER_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps a session's answer of spec-brief's under the common directory, with\n * the key it answers, and removes what was kept over a day ago. Whatever\n * fails is passed over: the answer is the one given whether or not it is\n * kept.\n */\nexport async function keepAnswer(commonDir: string, name: string, key: string, answer: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'answers');\n await writeAtomic(join(directory, name), `${JSON.stringify({ key, answer })}\\n`);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent, and so is the one that also removes a file kept a day ago\n // to the millisecond.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > ANSWER_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the next run starts spec-brief.\n }\n}\n\n/**\n * The answer kept under `name` for `key`, or `null` where there is none:\n * nothing there, something that is not a file, a text that is not what\n * `keepAnswer` writes, or another key's.\n */\nexport async function keptAnswer(commonDir: string, name: string, key: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'answers', name);\n try {\n // Only a file is read: a named pipe would be read without end.\n if (!(await stat(file)).isFile()) return null;\n // A text that holds null throws on the read, and any other that is no\n // object has no key: the catch answers for both. A Buffer parses as its\n // text, so the encoding is equivalent to its mutant.\n const kept: { readonly key?: unknown; readonly answer?: unknown } = JSON.parse(await readFile(file, 'utf8'));\n return kept.key === key && typeof kept.answer === 'string' ? kept.answer : null;\n } catch {\n // Falling out of the catch answers undefined, which no answer is read\n // from either: the mutant without the return is equivalent.\n return null;\n }\n}\n\n/** Whether a path that could not be read names nothing: it, or a directory on the way to it, is not there. */\nfunction nothingThere(error: unknown): boolean {\n const { code } = error as NodeJS.ErrnoException;\n return code === 'ENOENT' || code === 'ENOTDIR';\n}\n\n/**\n * Whether a file reads as listed: a file, never a pipe or a directory, whose\n * bytes have the digest; or, listed as not there, nothing at its path.\n */\nasync function fileReads(file: string, sha256: string | null, hash: Hash): Promise<boolean> {\n let there: Stats;\n try {\n there = await stat(file);\n } catch (error) {\n return sha256 === null && nothingThere(error);\n }\n // Only a file is read: a named pipe would be read without end. A file\n // listed as not there has no digest a file's bytes have.\n if (!there.isFile()) return false;\n return hash(await readFile(file)) === sha256;\n}\n\n/**\n * Whether a directory is there and holds the files listed, and no other: a\n * file as spec-brief lists one, an entry that is a file itself, where a\n * directory or a link is not one. What is no directory, a named pipe among\n * it, is refused by the listing, which throws. Listed as not there, nothing\n * may stand at its path in any form, a link that leads nowhere among them.\n */\nasync function entriesRead(directory: string, entries: readonly string[] | null): Promise<boolean> {\n if (entries === null) {\n try {\n await lstat(directory);\n return false;\n } catch (error) {\n return nothingThere(error);\n }\n }\n // The entries are names no two of which are one (inputsOf), and so are a\n // directory's: as many of each, every one of the one among the other, is\n // the same names, in whatever order the disk lists them.\n const names = (await readdir(directory, { withFileTypes: true })).flatMap((entry) => (entry.isFile() ? [entry.name] : []));\n const listed = new Set(entries);\n return names.length === listed.size && names.every((name) => listed.has(name));\n}\n\n/**\n * Whether every input of an answer reads as listed, with no process\n * started: each file by the digest of its bytes, or as not there; each\n * directory by its entries; and, where git was asked, the index of the work\n * tree's git directory and each ignore file. A path is read from the work\n * tree's top, or as it stands where it is absolute. Anything that cannot be\n * read, and an index that is not the one git reads for the work tree, is a\n * doubt, and answers `false` (ADR-0002).\n */\nexport async function readsAsListed(root: string, inputs: BriefInputs, hash: Hash, env: Readonly<Record<string, string | undefined>> = process.env): Promise<boolean> {\n const at = (path: string): string => (isAbsolute(path) ? path : join(root, ...path.split('/')));\n const git = inputs.git;\n let index: string | null = null;\n if (git !== null) {\n // The index of the work tree's own git directory, as the files git keeps\n // name it: not where git is sent elsewhere, GIT_DIR, GIT_WORK_TREE or a\n // worktree key (`exact`), GIT_INDEX_FILE or GIT_COMMON_DIR. A top git\n // named always holds a `.git`, so the test of none is the compiler's, and\n // a null read past it throws, which the caller reads as a doubt: that\n // mutant is equivalent. The top found is the work tree's own but where\n // git names a top that holds no `.git`, a work tree `core.worktree`\n // names away from its repository and inside another, whose index the\n // one found would be.\n const found = gitDirectories(root, env);\n if (found === null || !found.exact || env['GIT_INDEX_FILE'] !== undefined || env['GIT_COMMON_DIR'] !== undefined || found.top !== realSpelling(root)) return false;\n index = join(found.git, 'index');\n }\n // Made once every check is known to be made, so that none is left to fail\n // with no one reading it. A text put among them where git was asked\n // nothing is one `every` reads as true, so that mutant is equivalent.\n const checks = [\n ...inputs.files.map((file) => fileReads(at(file.path), file.sha256, hash)),\n ...inputs.directories.map((directory) => entriesRead(at(directory.path), directory.entries)),\n ...(git === null ? [] : [fileReads(index!, git.index, hash), ...git.ignores.map((file) => fileReads(at(file.path), file.sha256, hash))]),\n ];\n try {\n return (await Promise.all(checks)).every((reads) => reads);\n } catch {\n // Falling out of the catch answers undefined, which the caller reads as\n // false: the mutant without the return is equivalent.\n return false;\n }\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/**\n * A JSON object from a file: `null` when there is no file, `'unreadable'`\n * when it holds anything else. Only a file is read: a named pipe where a\n * settings file is expected was read without end, and neither doctor nor\n * init came back. What is neither a file nor a directory holds no JSON\n * object, as a directory holds none.\n */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n const there = standing(file);\n if (there === null) return null;\n if (!there.isFile()) return 'unreadable';\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n const value: unknown = JSON.parse(await readFile(file, 'utf8'));\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\n/** Whether a path names a directory that is there; one that cannot be read names none. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n // Each caller reads the answer with `!`, so the mutant that answers\n // nothing here is equivalent.\n return false;\n }\n}\n\n/** A file's text, or `null` where it cannot be read or is no file: a brief, a document one cites, a shim. */\nexport async function readText(file: string): Promise<string | null> {\n if (!isFile(file)) return null;\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, type Stats } from 'node:fs';\nimport { lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';\n\nimport type { BriefInputs } from './briefs.js';\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing. */\nfunction standing(path: string): Stats | null {\n try {\n return statSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a regular file stands at a path, links followed. Only one is read\n * where a file is expected: opened, a named pipe nothing writes to is read\n * without end, and one read as the process waits on it, as the harness reads\n * a few small files, stops every timer with it (ADR-0002).\n */\nexport function isFile(path: string): boolean {\n return standing(path)?.isFile() === true;\n}\n\n/**\n * Reads `.spec-harness.json` at the root, or the defaults when there is none.\n *\n * Only a file is read. A named pipe under the file's name was read without\n * end, by every command and by the hook before every write; and Node's words\n * for a directory, EISDIR, are not the harness's. A file that cannot be read\n * is said as that, with the reason: one the account may not read, or one\n * another process holds open on Windows, was said to be JSON that is not\n * valid, which sent a person to its text. What does not load is thrown with\n * the path it stands at, which the hook lets a write reach (ADR-0005).\n */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n const there = standing(file);\n if (there === null) return { config: DEFAULT_CONFIG, file: null };\n if (!there.isFile()) throw new ConfigError(`${CONFIG_FILE} is ${there.isDirectory() ? 'a directory' : 'neither a file nor a directory'}, not a configuration file`, file);\n let text: string;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n text = await readFile(file, 'utf8');\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} cannot be read: ${(error as Error).message}`, file);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`, file);\n }\n try {\n return { config: parseConfig(raw), file };\n } catch (error) {\n // parseConfig throws nothing but its own refusal, whose words are kept.\n throw new ConfigError((error as Error).message, file);\n }\n}\n\n/**\n * The real path of a file that stands at `path` under that name alone: a\n * file, not a link to one, and with no other name, since a write to a file\n * with two names writes both. `null` for anything else, and for nothing.\n */\nfunction soleFile(path: string): string | null {\n try {\n const entry = lstatSync(path);\n return entry.isFile() && entry.nlink === 1 ? realpathSync.native(path) : null;\n } catch {\n // Nothing at the path, or nothing the account may look at. Its callers\n // compare the answer with a real path, which neither `null` nor what an\n // empty block answers is, so that mutant is equivalent.\n return null;\n }\n}\n\n/**\n * Whether every path of a write names `file` and nothing else: a file that\n * stands under that name alone, which the path names as the hook resolves a\n * path to decide it ({@link realSpelling}), and as the filesystem resolves\n * it to write, which on Linux and macOS follows a link before a `..` where\n * the hook reads the `..` first. A path that only reads like it, a link to\n * it, a second name of it, and a file of another work tree are none of\n * these. `cwd` is what a relative path is read from, its words kept as\n * written.\n */\nexport function writesOnly(paths: readonly string[], file: string, cwd: string): boolean {\n // Where the file is none, no path's real spelling is `null`, so no path\n // names it.\n const own = soleFile(file);\n return paths.every((given) => {\n const path = isAbsolute(given) ? given : `${cwd}${sep}${given}`;\n return soleFile(path) === own && realSpelling(path) === own;\n });\n}\n\n/**\n * A path where the harness keeps its state that holds something else: said\n * in a line, exit 2, where Node's ENOTDIR and a stack sent a person looking\n * for a defect (spec-core ADR-0005).\n */\nexport class StateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StateError';\n }\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * The most links the written reading of a path follows. Linux follows at\n * most 40 in resolving a pathname, and fails the call with ELOOP past them\n * (path_resolution(7)), so no write reaches a file through more.\n */\nconst MOST_LINKS = 40;\n\n/**\n * A path's names, separated as the system separates them: Windows takes `/`\n * as well as `\\`, where Linux and macOS take a `\\` for a character of a name.\n * The sweep runs on Linux, where the mutants that read the pattern there, or\n * change it, are equivalent; the suite's Windows leg holds them.\n */\nfunction namesOf(path: string): string[] {\n return path.split(sep === '/' ? '/' : /[\\\\/]/);\n}\n\n/**\n * The real spelling of the file a write of `given` reaches, read from `cwd`\n * where it is relative; `null` where it reaches none, through more links\n * than the system follows or a loop of them. Linux reads a `..` after the\n * link before it, and macOS is read so, so `deep/../schema.ts`, `deep` a\n * link to `lib/x`, is written as `lib/schema.ts` (measured on Linux), where\n * {@link realSpelling} reads the `..` first and names `schema.ts`; Windows\n * reads the `..` first, as resolve does (measured, through a junction). And\n * a link to nothing is followed, since a write opens the file it names and\n * makes it there (measured on Linux and on GitHub's Windows runner), where\n * `realSpelling` names the link.\n * The names are walked one by one from the root, each link replaced by what\n * it names, so the directory reached holds no link, and `join` reads a `.`,\n * a `..` and an empty name from it as the system does; what is not there\n * yet is made as written.\n */\nexport function writtenSpelling(given: string, cwd: string): string | null {\n // On Linux, where the sweep runs, the mutant that reads every path as\n // Windows does is caught, and the one that reads every path as Linux does\n // is equivalent; the suite's Windows leg holds it. The root is taken off\n // the names as it is off a link's, below.\n const absolute = sep === '/' ? (isAbsolute(given) ? given : `${cwd}/${given}`) : resolve(cwd, given);\n const root = parse(absolute).root;\n const names = namesOf(absolute.slice(root.length));\n let at = root;\n let links = 0;\n while (names.length > 0) {\n const next = join(at, names.shift()!);\n let target: string;\n try {\n if (!lstatSync(next).isSymbolicLink()) {\n at = next;\n continue;\n }\n target = readlinkSync(next);\n } catch {\n // Nothing there, or nothing the account may look into: a write makes\n // what is not there as written, and reaches nothing past a directory\n // it may not look into.\n at = join(next, ...names);\n break;\n }\n links += 1;\n if (links > MOST_LINKS) return null;\n // A link names a path from the directory it stands in, or from a root,\n // which is taken off its names: on Linux and macOS the root is `/`,\n // whose name is empty and read as none, so keeping it there is\n // equivalent; on Windows it is the drive (a test holds it there).\n const top = parse(target).root;\n if (top !== '') at = top;\n names.unshift(...namesOf(target.slice(top.length)));\n }\n // The walk spells what exists as given, links followed; where case is\n // ignored, the entries' case is the real one. On Linux, where the sweep\n // runs, the two are one, so the mutant that leaves this out is equivalent\n // there (a test holds it on Windows and macOS).\n return realSpelling(at);\n}\n\n/**\n * Every file a write of `given` may reach, each by its real spelling: the\n * one the path reads as ({@link realSpelling}), and the one the system\n * writes ({@link writtenSpelling}) where that is another. The hook does not\n * know which of the two a tool writes, so it decides both (ADR-0005). A\n * path that reaches no file is read as it reads.\n */\nexport function reachedBy(given: string, cwd: string): string[] {\n const read = realSpelling(resolve(cwd, given));\n const written = writtenSpelling(given, cwd);\n return written === null || written === read ? [read] : [read, written];\n}\n\n/**\n * Whether `directory` is the one git names as the top of a work tree: the\n * two spelled alike once the filesystem has spelled the directory, links\n * and short names resolved, as git spells a top. A spelling that differs is\n * read as another directory: what git said from the one is then not taken\n * for what it would say from the other.\n */\nexport function isTop(directory: string, top: string): boolean {\n return realSpelling(directory).split(sep).join('/') === top;\n}\n\n/**\n * The repository's main work tree, where git keeps the repository as that\n * work tree's `.git`, as `git init` and `git clone` do and every linked\n * worktree's common directory then is: `null` for a common directory of any\n * other name, a bare repository's, a submodule's under its superproject's\n * `modules`, or one kept apart with `--separate-git-dir`. Read from the\n * name, with no process: it says only where an install is looked for.\n */\nexport function mainWorkTree(commonDir: string): string | null {\n return basename(commonDir) === '.git' ? dirname(commonDir) : null;\n}\n\n/** Whether a `.git` stands in `directory`, a repository's or the file that names one, as at the top of a work tree. */\nexport function holdsGit(directory: string): boolean {\n return existsSync(join(directory, '.git'));\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/**\n * The top of every work tree of the repository whose common directory is\n * `common`, `top` among them, read with no process: the main work tree,\n * whose `.git` the common directory is, and each linked worktree, by the\n * `gitdir` file git keeps for it under the common directory's `worktrees`,\n * which names the worktree's `.git` (gitrepository-layout(5)), absolute or\n * from its own directory. `null` where they cannot all be named: a common\n * directory of any other name, whose main work tree only its configuration\n * names, and an entry under `worktrees` that cannot be read.\n */\nexport function workTrees(top: string, common: string): string[] | null {\n const main = mainWorkTree(common);\n if (main === null) return null;\n const kept = join(common, 'worktrees');\n let names: string[] = [];\n try {\n names = readdirSync(kept);\n } catch (error) {\n // No such directory is no linked worktree, and anything else there is\n // one that cannot be read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return null;\n }\n const linked = names.map((name) => textOf(join(kept, name, 'gitdir')));\n if (linked.includes(null)) return null;\n // The line's end stands in the last name, the worktree's `.git`, which\n // dirname takes away with it.\n return [top, main, ...names.map((name, at) => dirname(resolve(kept, name, linked[at]!)))];\n}\n\n/**\n * Whether every path lies outside every work tree of the repository whose\n * top and common directory are given, as it reads and as the system writes\n * it ({@link reachedBy}): such a path is no file of the repository's, so no\n * brief of its decides it, whatever is wrong with the brief or the\n * configuration (ADR-0005). Where the work trees cannot all be named, a\n * path is not known to be outside them, and this is `false`.\n */\nexport function outsideEvery(paths: readonly string[], top: string, common: string, cwd: string): boolean {\n // A write that names no path is no write outside: no caller asks of\n // one, and a test holds the answer.\n if (paths.length === 0) return false;\n // A path in the work tree that was opened, as nearly every write's is,\n // is told with nothing of git's directory read; read anyway, `top` is\n // among the work trees, so the mutant without this test is equivalent.\n if (paths.some((path) => repositoryPath(path, top, cwd) !== null)) return false;\n // A path with a `..` in it was held here when the hook read a path one\n // way, its `..` before its links: `/elsewhere/link/../a.ts`, the link to\n // a directory of the work tree, read as outside and was written inside.\n // Both readings are read below, and such a path is held as it was all\n // the same (ADR-0005).\n if (paths.some((path) => path.split(/[\\\\/]/).includes('..'))) return false;\n // As a path reads and as the system writes it, a link to nothing among\n // its links: the file it reaches is outside too, or the path is not.\n const reached = paths.flatMap((path) => reachedBy(path, cwd));\n const trees = workTrees(top, common);\n return trees !== null && reached.every((path) => trees.every((tree) => repositoryPath(path, tree, cwd) === null));\n}\n\n/**\n * The harness's own directory under the git common directory, created on\n * demand (ADR-0003). Where something that is no directory stands at its path,\n * or at one between it and git's, Node refuses to make it, with ENOTDIR or\n * EEXIST, and the harness says which path holds what.\n */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n try {\n await mkdir(directory, { recursive: true });\n } catch (error) {\n const { code } = error as NodeJS.ErrnoException;\n if (code !== 'ENOTDIR' && code !== 'EEXIST') throw error;\n // mkdir refuses for this alone, so one of the steps is there and is no\n // directory, and every step before it is one.\n const steps = ['spec-harness', ...parts].map((_, at, all) => join(commonDir, ...all.slice(0, at + 1)));\n const there = steps.find((step) => !standing(step)!.isDirectory())!;\n throw new StateError(`${there} is not a directory, where spec-harness keeps its state in one: move it away, and run the command again`);\n }\n return directory;\n}\n\n/**\n * A work tree and its git directories as the files git keeps name them, read\n * with no process: the nearest directory at or above `root` that holds a\n * `.git`; the git directory, that `.git` or the one a `.git` file names\n * (`gitdir: <path>`, relative to the file's directory); and the common\n * directory, the one that git directory's `commondir` file names, relative\n * to it, or the git directory itself (gitrepository-layout(5)). `exact` is\n * whether git, asked, would name the same work tree: not where\n * `GIT_DIR` or `GIT_WORK_TREE` sends it elsewhere, and not where the\n * repository's configuration names a `worktree`, which `core.worktree`\n * moves it to (git-config(1)), or keeps one of its own for a worktree.\n * `null` where no directory up to the top of the filesystem holds one, or\n * a `.git` file names none.\n */\nexport function gitDirectories(root: string, env: Readonly<Record<string, string | undefined>>): { readonly top: string; readonly git: string; readonly common: string; readonly exact: boolean } | null {\n let top = realSpelling(root);\n let found: Stats | null = null;\n for (;;) {\n found = standing(join(top, '.git'));\n if (found !== null) break;\n const parent = dirname(top);\n if (parent === top) return null;\n top = parent;\n }\n const dotGit = join(top, '.git');\n let gitDir = dotGit;\n if (found.isFile()) {\n // The path is the rest of the line: `.` stops at a line's end, a\n // carriage return among them, so the mutant that drops `$` is\n // equivalent; and a Buffer is read as its text, so is the encoding's.\n const named = /^gitdir: (.+)$/m.exec(readFileSync(dotGit, 'utf8'))?.[1];\n if (named === undefined) return null;\n gitDir = resolve(top, named);\n }\n const commondir = textOf(join(gitDir, 'commondir'));\n const common = commondir === null ? gitDir : resolve(gitDir, commondir.trim());\n const moved = env['GIT_DIR'] !== undefined || env['GIT_WORK_TREE'] !== undefined;\n // No configuration holds no key, as a text without one does: the mutant\n // of the empty text is equivalent.\n const elsewhere = /^\\s*worktree\\s*=/im.test(textOf(join(common, 'config')) ?? '') || existsSync(join(gitDir, 'config.worktree'));\n return { top, git: gitDir, common, exact: !moved && !elsewhere };\n}\n\n/** A file's text, or `null` where it cannot be read or is no file. */\nfunction textOf(file: string): string | null {\n if (!isFile(file)) return null;\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return null;\n }\n}\n\n/**\n * How long a call's decisions are kept for a run after the write that does\n * not come: a call refused by the person, one that failed, one stopped\n * (ADR-0002). A day, since a call waits on a person's prompt for as long as\n * the person is away, and an entry removed too soon costs only that call's\n * answer being decided again.\n */\nexport const CALL_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps what a run before a write decided, under the common directory, for\n * the run after it, and removes what no run after a write read in a day.\n * Nothing here may change the answer before the write: whatever fails is\n * passed over, and the run after the write then decides as it did.\n */\nexport async function keepCall(commonDir: string, name: string, text: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'calls');\n await writeAtomic(join(directory, name), text);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > CALL_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the call is decided again after the write.\n }\n}\n\n/** What was kept for a call, taken away as it is read; `null` where nothing was. */\nexport async function takeCall(commonDir: string, name: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'calls', name);\n // What is no file holds nothing kept, and is written over at the next write.\n if (!isFile(file)) return null;\n try {\n const text = await readFile(file, 'utf8');\n // Taken away by another run since it was read, the mutants of `force`\n // decide the call again, which answers as the text read does: they are\n // equivalent.\n await rm(file, { force: true });\n return text;\n } catch {\n return null;\n }\n}\n\n/** SHA-256 of a text or of bytes, in hex. */\nexport type Hash = (data: string | Uint8Array) => string;\n\n/** SHA-256, loaded where something is hashed: a run that keeps no answer loads no hash (ADR-0002). */\nexport async function hasher(): Promise<Hash> {\n const { createHash } = await import('node:crypto');\n return (data) => createHash('sha256').update(data).digest('hex');\n}\n\n/**\n * How long a session's answer of spec-brief's is kept once it was last\n * kept (ADR-0003). A day, as a call's: an answer removed costs one start of\n * spec-brief, and one kept is a few kilobytes.\n */\nexport const ANSWER_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps a session's answer of spec-brief's under the common directory, with\n * the key it answers, and removes what was kept over a day ago. Whatever\n * fails is passed over: the answer is the one given whether or not it is\n * kept.\n */\nexport async function keepAnswer(commonDir: string, name: string, key: string, answer: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'answers');\n await writeAtomic(join(directory, name), `${JSON.stringify({ key, answer })}\\n`);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent, and so is the one that also removes a file kept a day ago\n // to the millisecond.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > ANSWER_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the next run starts spec-brief.\n }\n}\n\n/**\n * The answer kept under `name` for `key`, or `null` where there is none:\n * nothing there, something that is not a file, a text that is not what\n * `keepAnswer` writes, or another key's.\n */\nexport async function keptAnswer(commonDir: string, name: string, key: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'answers', name);\n try {\n // Only a file is read: a named pipe would be read without end.\n if (!(await stat(file)).isFile()) return null;\n // A text that holds null throws on the read, and any other that is no\n // object has no key: the catch answers for both. A Buffer parses as its\n // text, so the encoding is equivalent to its mutant.\n const kept: { readonly key?: unknown; readonly answer?: unknown } = JSON.parse(await readFile(file, 'utf8'));\n return kept.key === key && typeof kept.answer === 'string' ? kept.answer : null;\n } catch {\n // Falling out of the catch answers undefined, which no answer is read\n // from either: the mutant without the return is equivalent.\n return null;\n }\n}\n\n/** Whether a path that could not be read names nothing: it, or a directory on the way to it, is not there. */\nfunction nothingThere(error: unknown): boolean {\n const { code } = error as NodeJS.ErrnoException;\n return code === 'ENOENT' || code === 'ENOTDIR';\n}\n\n/**\n * Whether a file reads as listed: a file, never a pipe or a directory, whose\n * bytes have the digest; or, listed as not there, nothing at its path.\n */\nasync function fileReads(file: string, sha256: string | null, hash: Hash): Promise<boolean> {\n let there: Stats;\n try {\n there = await stat(file);\n } catch (error) {\n return sha256 === null && nothingThere(error);\n }\n // Only a file is read: a named pipe would be read without end. A file\n // listed as not there has no digest a file's bytes have.\n if (!there.isFile()) return false;\n return hash(await readFile(file)) === sha256;\n}\n\n/**\n * Whether a directory is there and holds the files listed, and no other: a\n * file as spec-brief lists one, an entry that is a file itself, where a\n * directory or a link is not one. What is no directory, a named pipe among\n * it, is refused by the listing, which throws. Listed as not there, nothing\n * may stand at its path in any form, a link that leads nowhere among them.\n */\nasync function entriesRead(directory: string, entries: readonly string[] | null): Promise<boolean> {\n if (entries === null) {\n try {\n await lstat(directory);\n return false;\n } catch (error) {\n return nothingThere(error);\n }\n }\n // The entries are names no two of which are one (inputsOf), and so are a\n // directory's: as many of each, every one of the one among the other, is\n // the same names, in whatever order the disk lists them.\n const names = (await readdir(directory, { withFileTypes: true })).flatMap((entry) => (entry.isFile() ? [entry.name] : []));\n const listed = new Set(entries);\n return names.length === listed.size && names.every((name) => listed.has(name));\n}\n\n/**\n * Whether every input of an answer reads as listed, with no process\n * started: each file by the digest of its bytes, or as not there; each\n * directory by its entries; and, where git was asked, the index of the work\n * tree's git directory and each ignore file. A path is read from the work\n * tree's top, or as it stands where it is absolute. Anything that cannot be\n * read, and an index that is not the one git reads for the work tree, is a\n * doubt, and answers `false` (ADR-0002).\n */\nexport async function readsAsListed(root: string, inputs: BriefInputs, hash: Hash, env: Readonly<Record<string, string | undefined>> = process.env): Promise<boolean> {\n const at = (path: string): string => (isAbsolute(path) ? path : join(root, ...path.split('/')));\n const git = inputs.git;\n let index: string | null = null;\n if (git !== null) {\n // The index of the work tree's own git directory, as the files git keeps\n // name it: not where git is sent elsewhere, GIT_DIR, GIT_WORK_TREE or a\n // worktree key (`exact`), GIT_INDEX_FILE or GIT_COMMON_DIR. A top git\n // named always holds a `.git`, so the test of none is the compiler's, and\n // a null read past it throws, which the caller reads as a doubt: that\n // mutant is equivalent. The top found is the work tree's own but where\n // git names a top that holds no `.git`, a work tree `core.worktree`\n // names away from its repository and inside another, whose index the\n // one found would be.\n const found = gitDirectories(root, env);\n if (found === null || !found.exact || env['GIT_INDEX_FILE'] !== undefined || env['GIT_COMMON_DIR'] !== undefined || found.top !== realSpelling(root)) return false;\n index = join(found.git, 'index');\n }\n // Made once every check is known to be made, so that none is left to fail\n // with no one reading it. A text put among them where git was asked\n // nothing is one `every` reads as true, so that mutant is equivalent.\n const checks = [\n ...inputs.files.map((file) => fileReads(at(file.path), file.sha256, hash)),\n ...inputs.directories.map((directory) => entriesRead(at(directory.path), directory.entries)),\n ...(git === null ? [] : [fileReads(index!, git.index, hash), ...git.ignores.map((file) => fileReads(at(file.path), file.sha256, hash))]),\n ];\n try {\n return (await Promise.all(checks)).every((reads) => reads);\n } catch {\n // Falling out of the catch answers undefined, which the caller reads as\n // false: the mutant without the return is equivalent.\n return false;\n }\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/**\n * A JSON object from a file: `null` when there is no file, `'unreadable'`\n * when it holds anything else. Only a file is read: a named pipe where a\n * settings file is expected was read without end, and neither doctor nor\n * init came back. What is neither a file nor a directory holds no JSON\n * object, as a directory holds none.\n */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n const there = standing(file);\n if (there === null) return null;\n if (!there.isFile()) return 'unreadable';\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n const value: unknown = JSON.parse(await readFile(file, 'utf8'));\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\n/** Whether a path names a directory that is there; one that cannot be read names none. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n // Each caller reads the answer with `!`, so the mutant that answers\n // nothing here is equivalent.\n return false;\n }\n}\n\n/** A file's text, or `null` where it cannot be read or is no file: a brief, a document one cites, a shim. */\nexport async function readText(file: string): Promise<string | null> {\n if (!isFile(file)) return null;\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
|
package/dist/git.d.ts
CHANGED
|
@@ -31,6 +31,18 @@ export declare const ANSWER_MS = 20000;
|
|
|
31
31
|
* write.
|
|
32
32
|
*/
|
|
33
33
|
export declare const WORK_MS = 600000;
|
|
34
|
+
/** Runs `work` with every process it starts given no more than what is left of `ms` from now. */
|
|
35
|
+
export declare function withBudget<T>(ms: number, work: () => Promise<T>): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* The time a process is given, `limit`, or what is left of the budget of
|
|
38
|
+
* the run it is started in where that is less, with the words that say
|
|
39
|
+
* which: the run's budget is said as such, so that a write held at it says
|
|
40
|
+
* why. A run with nothing left is given 0, and starts nothing.
|
|
41
|
+
*/
|
|
42
|
+
export declare function timeFor(limit: number): {
|
|
43
|
+
readonly ms: number;
|
|
44
|
+
readonly said: string;
|
|
45
|
+
};
|
|
34
46
|
/**
|
|
35
47
|
* Whether a process ended because the harness stopped it at its time
|
|
36
48
|
* limit: Node kills it then and says so with `killed`. It kills one for
|
|
@@ -150,6 +162,32 @@ export declare function changedPaths(cwd: string): Promise<string[]>;
|
|
|
150
162
|
export declare function stagedChanges(cwd: string): Promise<FileChange[]>;
|
|
151
163
|
/** A file's content at a revision, or `null` when it does not exist there. */
|
|
152
164
|
export declare function show(ref: string, path: string, cwd: string): Promise<string | null>;
|
|
165
|
+
/**
|
|
166
|
+
* Whether a commit's tree holds `path`, a path from the top of the work tree
|
|
167
|
+
* read as written: `true` where git lists it, `false` where git lists
|
|
168
|
+
* nothing for it, and `null` where git could not say, for a commit it does
|
|
169
|
+
* not have among them. {@link show} answers `null` for a file that is not
|
|
170
|
+
* there and for a git that failed alike, which can only make an answer
|
|
171
|
+
* stricter; this tells the two apart, so that an answer may be loosened by a
|
|
172
|
+
* file that git says is not there.
|
|
173
|
+
*
|
|
174
|
+
* ls-tree reads its paths as patterns, from the directory it is started in
|
|
175
|
+
* (git-ls-tree(1), Description, as Git for Windows 2.55.0 has it, read
|
|
176
|
+
* 2026-10-24): `--full-tree` has it read them from the top, and
|
|
177
|
+
* `--literal-pathspecs` with no globbing and no magic (git(1), Options), so
|
|
178
|
+
* a name such as `:(top)x.md` names that file, where ls-tree would read it
|
|
179
|
+
* as `x.md`.
|
|
180
|
+
*/
|
|
181
|
+
export declare function holdsPath(commit: string, path: string, cwd: string): Promise<boolean | null>;
|
|
182
|
+
/**
|
|
183
|
+
* Whether the history is cut short, as a clone or a fetch with `--depth`
|
|
184
|
+
* leaves it, where a merge base git finds can be an older commit than the
|
|
185
|
+
* one the whole history has; `null` where git could not say. rev-parse
|
|
186
|
+
* learned the question in 2.15.0 (its release notes), and prints `true` or
|
|
187
|
+
* `false` (git-rev-parse(1), Options for Files, read 2026-10-24); anything
|
|
188
|
+
* else is no answer.
|
|
189
|
+
*/
|
|
190
|
+
export declare function isShallow(cwd: string): Promise<boolean | null>;
|
|
153
191
|
/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
|
|
154
192
|
export declare function isUncommitted(sha: string): boolean;
|
|
155
193
|
/**
|
package/dist/git.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* call is an argument vector, never a shell string, so a path or a ref cannot
|
|
7
7
|
* become a command.
|
|
8
8
|
*/
|
|
9
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
10
|
import { execFile } from 'node:child_process';
|
|
10
11
|
/**
|
|
11
12
|
* How long a process the harness asks a question of is given to answer:
|
|
@@ -26,6 +27,25 @@ export const ANSWER_MS = 20_000;
|
|
|
26
27
|
* write.
|
|
27
28
|
*/
|
|
28
29
|
export const WORK_MS = 600_000;
|
|
30
|
+
/** The run a process is started in, where it has a budget: its length, and when it ends, as `performance.now()` reads it. */
|
|
31
|
+
const budget = new AsyncLocalStorage();
|
|
32
|
+
/** Runs `work` with every process it starts given no more than what is left of `ms` from now. */
|
|
33
|
+
export function withBudget(ms, work) {
|
|
34
|
+
return budget.run({ ms, end: performance.now() + ms }, work);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The time a process is given, `limit`, or what is left of the budget of
|
|
38
|
+
* the run it is started in where that is less, with the words that say
|
|
39
|
+
* which: the run's budget is said as such, so that a write held at it says
|
|
40
|
+
* why. A run with nothing left is given 0, and starts nothing.
|
|
41
|
+
*/
|
|
42
|
+
export function timeFor(limit) {
|
|
43
|
+
const run = budget.getStore();
|
|
44
|
+
const left = run === undefined ? limit : run.end - performance.now();
|
|
45
|
+
if (left >= limit)
|
|
46
|
+
return { ms: limit, said: `${limit / 1000} seconds` };
|
|
47
|
+
return { ms: Math.max(0, Math.ceil(left)), said: `the ${run.ms / 1000} seconds the hook's run is given` };
|
|
48
|
+
}
|
|
29
49
|
/**
|
|
30
50
|
* Whether a process ended because the harness stopped it at its time
|
|
31
51
|
* limit: Node kills it then and says so with `killed`. It kills one for
|
|
@@ -47,12 +67,15 @@ function subcommand(args) {
|
|
|
47
67
|
* branch, under which the hook checks no write.
|
|
48
68
|
*/
|
|
49
69
|
export function git(args, cwd, limit = ANSWER_MS) {
|
|
70
|
+
const { ms, said } = timeFor(limit);
|
|
71
|
+
if (ms === 0)
|
|
72
|
+
return Promise.reject(new GitError(`git ${subcommand(args)} was not started: ${said} had passed`));
|
|
50
73
|
return new Promise((resolve, reject) => {
|
|
51
74
|
// windowsHide only keeps a console window from flashing up on Windows,
|
|
52
75
|
// and the sweep runs on Linux, where its mutant is equivalent.
|
|
53
|
-
execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout:
|
|
76
|
+
execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout: ms }, (error, stdout, stderr) => {
|
|
54
77
|
if (stoppedAtLimit(error)) {
|
|
55
|
-
reject(new GitError(`git ${subcommand(args)} did not answer within ${
|
|
78
|
+
reject(new GitError(`git ${subcommand(args)} did not answer within ${said}, and was stopped`));
|
|
56
79
|
return;
|
|
57
80
|
}
|
|
58
81
|
// Every caller tells 0 from any other code, never one failure from
|
|
@@ -271,6 +294,40 @@ export async function show(ref, path, cwd) {
|
|
|
271
294
|
const run = await git(['show', `${ref}:${path}`], cwd);
|
|
272
295
|
return run.code === 0 ? run.stdout : null;
|
|
273
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Whether a commit's tree holds `path`, a path from the top of the work tree
|
|
299
|
+
* read as written: `true` where git lists it, `false` where git lists
|
|
300
|
+
* nothing for it, and `null` where git could not say, for a commit it does
|
|
301
|
+
* not have among them. {@link show} answers `null` for a file that is not
|
|
302
|
+
* there and for a git that failed alike, which can only make an answer
|
|
303
|
+
* stricter; this tells the two apart, so that an answer may be loosened by a
|
|
304
|
+
* file that git says is not there.
|
|
305
|
+
*
|
|
306
|
+
* ls-tree reads its paths as patterns, from the directory it is started in
|
|
307
|
+
* (git-ls-tree(1), Description, as Git for Windows 2.55.0 has it, read
|
|
308
|
+
* 2026-10-24): `--full-tree` has it read them from the top, and
|
|
309
|
+
* `--literal-pathspecs` with no globbing and no magic (git(1), Options), so
|
|
310
|
+
* a name such as `:(top)x.md` names that file, where ls-tree would read it
|
|
311
|
+
* as `x.md`.
|
|
312
|
+
*/
|
|
313
|
+
export async function holdsPath(commit, path, cwd) {
|
|
314
|
+
const run = await git(['--literal-pathspecs', 'ls-tree', '-z', '--full-tree', '--name-only', commit, '--', path], cwd);
|
|
315
|
+
return run.code === 0 ? run.stdout !== '' : null;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Whether the history is cut short, as a clone or a fetch with `--depth`
|
|
319
|
+
* leaves it, where a merge base git finds can be an older commit than the
|
|
320
|
+
* one the whole history has; `null` where git could not say. rev-parse
|
|
321
|
+
* learned the question in 2.15.0 (its release notes), and prints `true` or
|
|
322
|
+
* `false` (git-rev-parse(1), Options for Files, read 2026-10-24); anything
|
|
323
|
+
* else is no answer.
|
|
324
|
+
*/
|
|
325
|
+
export async function isShallow(cwd) {
|
|
326
|
+
const answer = await value(['rev-parse', '--is-shallow-repository'], cwd);
|
|
327
|
+
if (answer === 'true')
|
|
328
|
+
return true;
|
|
329
|
+
return answer === 'false' ? false : null;
|
|
330
|
+
}
|
|
274
331
|
/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
|
|
275
332
|
export function isUncommitted(sha) {
|
|
276
333
|
return /^0+$/.test(sha);
|