@claudexor/workspace 4.8.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anchor-store.d.ts +20 -9
- package/dist/anchor-store.d.ts.map +1 -1
- package/dist/anchor-store.js +96 -16
- package/dist/anchor-store.js.map +1 -1
- package/dist/capture-failure.d.ts.map +1 -1
- package/dist/capture-failure.js +7 -2
- package/dist/capture-failure.js.map +1 -1
- package/dist/capture-inventory.d.ts +10 -0
- package/dist/capture-inventory.d.ts.map +1 -0
- package/dist/capture-inventory.js +63 -0
- package/dist/capture-inventory.js.map +1 -0
- package/dist/capture-objects.d.ts +32 -0
- package/dist/capture-objects.d.ts.map +1 -0
- package/dist/capture-objects.js +129 -0
- package/dist/capture-objects.js.map +1 -0
- package/dist/cleanup-fault.d.ts +18 -0
- package/dist/cleanup-fault.d.ts.map +1 -0
- package/dist/cleanup-fault.js +45 -0
- package/dist/cleanup-fault.js.map +1 -0
- package/dist/continuation-evidence.d.ts +61 -15
- package/dist/continuation-evidence.d.ts.map +1 -1
- package/dist/continuation-evidence.js +151 -36
- package/dist/continuation-evidence.js.map +1 -1
- package/dist/continuation-facts.d.ts +93 -0
- package/dist/continuation-facts.d.ts.map +1 -0
- package/dist/continuation-facts.js +162 -0
- package/dist/continuation-facts.js.map +1 -0
- package/dist/directory-workspace.d.ts.map +1 -1
- package/dist/directory-workspace.js +1 -3
- package/dist/directory-workspace.js.map +1 -1
- package/dist/files-io.d.ts.map +1 -1
- package/dist/files-io.js +1 -7
- package/dist/files-io.js.map +1 -1
- package/dist/fs-presence.d.ts +12 -0
- package/dist/fs-presence.d.ts.map +1 -0
- package/dist/fs-presence.js +35 -0
- package/dist/fs-presence.js.map +1 -0
- package/dist/git-nested-repositories.d.ts.map +1 -1
- package/dist/git-nested-repositories.js +11 -2
- package/dist/git-nested-repositories.js.map +1 -1
- package/dist/git-observed.d.ts +75 -0
- package/dist/git-observed.d.ts.map +1 -0
- package/dist/git-observed.js +172 -0
- package/dist/git-observed.js.map +1 -0
- package/dist/git-result.d.ts +36 -0
- package/dist/git-result.d.ts.map +1 -0
- package/dist/git-result.js +96 -0
- package/dist/git-result.js.map +1 -0
- package/dist/git-run.d.ts +11 -0
- package/dist/git-run.d.ts.map +1 -1
- package/dist/git-run.js +24 -0
- package/dist/git-run.js.map +1 -1
- package/dist/git-snapshot.d.ts +40 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +238 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-transient-capture.d.ts +63 -16
- package/dist/git-transient-capture.d.ts.map +1 -1
- package/dist/git-transient-capture.js +170 -57
- package/dist/git-transient-capture.js.map +1 -1
- package/dist/git.d.ts +46 -21
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +164 -103
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lanes.d.ts +2 -1
- package/dist/lanes.d.ts.map +1 -1
- package/dist/lanes.js +7 -6
- package/dist/lanes.js.map +1 -1
- package/dist/manager.d.ts +21 -7
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +30 -17
- package/dist/manager.js.map +1 -1
- package/dist/object-compose.d.ts +58 -0
- package/dist/object-compose.d.ts.map +1 -0
- package/dist/object-compose.js +444 -0
- package/dist/object-compose.js.map +1 -0
- package/dist/object-materialize.d.ts +52 -0
- package/dist/object-materialize.d.ts.map +1 -0
- package/dist/object-materialize.js +467 -0
- package/dist/object-materialize.js.map +1 -0
- package/dist/object-occupied.d.ts +27 -0
- package/dist/object-occupied.d.ts.map +1 -0
- package/dist/object-occupied.js +131 -0
- package/dist/object-occupied.js.map +1 -0
- package/dist/object-revert.d.ts +45 -0
- package/dist/object-revert.d.ts.map +1 -0
- package/dist/object-revert.js +265 -0
- package/dist/object-revert.js.map +1 -0
- package/dist/object-saved-files.d.ts +50 -0
- package/dist/object-saved-files.d.ts.map +1 -0
- package/dist/object-saved-files.js +176 -0
- package/dist/object-saved-files.js.map +1 -0
- package/dist/object-sparse.d.ts +22 -0
- package/dist/object-sparse.d.ts.map +1 -0
- package/dist/object-sparse.js +72 -0
- package/dist/object-sparse.js.map +1 -0
- package/dist/object-store-leases.d.ts +41 -0
- package/dist/object-store-leases.d.ts.map +1 -0
- package/dist/object-store-leases.js +277 -0
- package/dist/object-store-leases.js.map +1 -0
- package/dist/object-store-roots.d.ts +25 -0
- package/dist/object-store-roots.d.ts.map +1 -0
- package/dist/object-store-roots.js +114 -0
- package/dist/object-store-roots.js.map +1 -0
- package/dist/object-store.d.ts +110 -0
- package/dist/object-store.d.ts.map +1 -0
- package/dist/object-store.js +404 -0
- package/dist/object-store.js.map +1 -0
- package/dist/object-transfer.d.ts +41 -0
- package/dist/object-transfer.d.ts.map +1 -0
- package/dist/object-transfer.js +267 -0
- package/dist/object-transfer.js.map +1 -0
- package/dist/plain-diff-capture.d.ts +1 -2
- package/dist/plain-diff-capture.d.ts.map +1 -1
- package/dist/plain-diff-capture.js +2 -7
- package/dist/plain-diff-capture.js.map +1 -1
- package/dist/plain-diff.d.ts +0 -4
- package/dist/plain-diff.d.ts.map +1 -1
- package/dist/plain-diff.js +2 -44
- package/dist/plain-diff.js.map +1 -1
- package/dist/raw-patch.d.ts.map +1 -1
- package/dist/raw-patch.js +5 -8
- package/dist/raw-patch.js.map +1 -1
- package/dist/run-expiry.d.ts +3 -0
- package/dist/run-expiry.d.ts.map +1 -0
- package/dist/run-expiry.js +15 -0
- package/dist/run-expiry.js.map +1 -0
- package/dist/thread-tree.d.ts +36 -2
- package/dist/thread-tree.d.ts.map +1 -1
- package/dist/thread-tree.js +87 -11
- package/dist/thread-tree.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, lstatSync, mkdtempSync, readlinkSync, renameSync, rmSync, } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { classifyGitFailure, diskProgress, gitObserved, GitOperationError, proofStallMs, } from "./git-observed.js";
|
|
5
|
+
import { withDisposableScratch } from "./cleanup-fault.js";
|
|
6
|
+
import { lstatPresent } from "./fs-presence.js";
|
|
7
|
+
import { SCRATCH_INDEX_CONFIG, STAT_TRUST_OFF } from "./git-run.js";
|
|
8
|
+
import { markedEntries } from "./git-snapshot.js";
|
|
9
|
+
import { parseRawRecords } from "./object-compose.js";
|
|
10
|
+
import { aliasJudge, byFold, foldsOnto, occupiedPaths } from "./object-occupied.js";
|
|
11
|
+
import { sparseCheckoutRefusal } from "./object-sparse.js";
|
|
12
|
+
import { assertSpaceFor } from "./object-transfer.js";
|
|
13
|
+
const GIT_FAULT_REFUSAL = {
|
|
14
|
+
capture_space_insufficient: "space_insufficient",
|
|
15
|
+
capture_git_stalled: "git_stalled",
|
|
16
|
+
capture_git_signal: "git_signal",
|
|
17
|
+
};
|
|
18
|
+
/** The typed refusal of a Git child's failure on an object path. */
|
|
19
|
+
export function gitFaultRefusalCode(code) {
|
|
20
|
+
return GIT_FAULT_REFUSAL[code] ?? "git_failed";
|
|
21
|
+
}
|
|
22
|
+
/** A Git child's typed failure, or a filesystem fault as one (its scratch: a full disk). */
|
|
23
|
+
function gitOrFilesystemFault(error) {
|
|
24
|
+
if (error instanceof GitOperationError)
|
|
25
|
+
return error;
|
|
26
|
+
const errno = error?.code;
|
|
27
|
+
if (typeof errno !== "string")
|
|
28
|
+
return null;
|
|
29
|
+
return new GitOperationError(errno === "ENOSPC" || errno === "EDQUOT" ? "capture_space_insufficient" : "capture_failed", error.message);
|
|
30
|
+
}
|
|
31
|
+
export { proofStallMs } from "./git-observed.js";
|
|
32
|
+
/** How `read-tree -m -u` refuses before its first write (its checks run first;
|
|
33
|
+
* object-path Git speaks the C locale). It only names the refusal: whether the
|
|
34
|
+
* checkout wrote anything is measured (`wroteAny`), since a path name in a
|
|
35
|
+
* partial checkout's message can read like a refusal. */
|
|
36
|
+
const REFUSED_BEFORE_WRITE = /not uptodate|would be overwritten|would be removed|would lose untracked|Cannot merge|index\.lock|failed to unpack tree|Refusing to remove/i;
|
|
37
|
+
/** What lstat sees at `path` (a checkout replaces a file it writes, so its
|
|
38
|
+
* identity changes; nothing is re-hashed through a filter). */
|
|
39
|
+
function stamp(worktree, path) {
|
|
40
|
+
try {
|
|
41
|
+
const stat = lstatSync(join(worktree, path), { bigint: true, throwIfNoEntry: false });
|
|
42
|
+
return stat
|
|
43
|
+
? `${stat.mode}:${stat.size}:${stat.ino}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
44
|
+
: "absent";
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return `error:${error.code ?? "unknown"}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** What lstat sees at `paths` in bytes (what a refresh reads at most). */
|
|
51
|
+
function diskBytes(worktree, paths) {
|
|
52
|
+
let bytes = 0;
|
|
53
|
+
for (const path of paths) {
|
|
54
|
+
try {
|
|
55
|
+
bytes += lstatSync(join(worktree, path), { throwIfNoEntry: false })?.size ?? 0;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Unreadable here: Git will say so when it gets there.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return bytes;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Refuse a checkout that cannot fit, before its first write: it would stop partway,
|
|
65
|
+
* and its take-back needs space too. A replaced file makes room just before its
|
|
66
|
+
* new content is written, so each path needs only what it grows by; a filter can
|
|
67
|
+
* change sizes, which the margin of `assertSpaceFor` covers. Returns the new bytes.
|
|
68
|
+
*/
|
|
69
|
+
async function assertWriteFits(worktree, records, git) {
|
|
70
|
+
const written = records.filter((record) => /^(?:100|120)/.test(record.newMode));
|
|
71
|
+
if (written.length === 0)
|
|
72
|
+
return 0;
|
|
73
|
+
const listed = await git(["cat-file", "--batch-check=%(objectname) %(objectsize)"], "cat-file --batch-check (checkout)", { input: `${[...new Set(written.map((record) => record.newOid))].join("\n")}\n` });
|
|
74
|
+
const sizes = new Map(listed.stdout
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((line) => line.split(" "))
|
|
77
|
+
.filter(([, size]) => size && /^\d+$/.test(size))
|
|
78
|
+
.map(([oid, size]) => [oid, Number(size)]));
|
|
79
|
+
let total = 0;
|
|
80
|
+
let growth = 0;
|
|
81
|
+
for (const record of written) {
|
|
82
|
+
const size = sizes.get(record.newOid) ?? 0;
|
|
83
|
+
total += size;
|
|
84
|
+
growth += Math.max(0, size - diskBytes(worktree, [record.src]));
|
|
85
|
+
}
|
|
86
|
+
assertSpaceFor(worktree, growth, "the checkout");
|
|
87
|
+
return total;
|
|
88
|
+
}
|
|
89
|
+
function refusal(code, message, paths = []) {
|
|
90
|
+
const sorted = [...new Set(paths)].sort();
|
|
91
|
+
return {
|
|
92
|
+
code,
|
|
93
|
+
message: message.slice(0, 4_000),
|
|
94
|
+
paths: sorted.slice(0, 200),
|
|
95
|
+
pathCount: sorted.length,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** The refusal of a write over what Git does not track, naming it. */
|
|
99
|
+
function occupiedRefusal(worktree, records) {
|
|
100
|
+
const occupied = occupiedPaths(worktree, records);
|
|
101
|
+
return occupied.length === 0
|
|
102
|
+
? null
|
|
103
|
+
: refusal("delivery_path_occupied", `a path this operation writes holds what Git does not track (an ignored file, for one), and writing would destroy it; nothing was written: ${occupied.slice(0, 20).join(", ")}`, occupied);
|
|
104
|
+
}
|
|
105
|
+
/** The same refusals for a dry run: what a checkout from `from` to `to` would answer
|
|
106
|
+
* here before writing (a path holding what Git does not track, or one outside the
|
|
107
|
+
* sparse-checkout patterns). */
|
|
108
|
+
export async function occupiedByWrite(worktree, from, to, env = {}) {
|
|
109
|
+
const listed = await gitObserved(worktree, ["diff-tree", "-r", "--raw", "-z", "--no-renames", "--no-abbrev", from, to], { env, label: "diff-tree --raw (dry run)" });
|
|
110
|
+
const records = parseRawRecords(listed.stdout);
|
|
111
|
+
return (occupiedRefusal(worktree, records) ??
|
|
112
|
+
(await sparseCheckoutRefusal(worktree, records.map((record) => record.src), "delivery", env)));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Write `to` over a worktree that holds `from` (rev 11 C): a two-tree
|
|
116
|
+
* `read-tree -m -u` that refuses "not uptodate" when a path it must change no
|
|
117
|
+
* longer holds `from` (a live preimage recheck) and streams big blobs to disk.
|
|
118
|
+
* It runs with the sparse-checkout patterns off: with them on, Git reapplies them
|
|
119
|
+
* to every entry and deletes a clean file outside them that holds no skip-worktree
|
|
120
|
+
* mark (a conflict resolved there), a path this write never addressed. Off, only
|
|
121
|
+
* the paths `from` and `to` differ in move; every other entry keeps its file, and its
|
|
122
|
+
* mark unless Git itself clears it (Git clears the skip-worktree mark of a present
|
|
123
|
+
* file when it reads a sparse checkout's index). What Git does not track is refused
|
|
124
|
+
* first: a file at a path `to` adds (ignored, so outside `from`) would be
|
|
125
|
+
* overwritten, and no revert could bring it back. So is an addressed path outside
|
|
126
|
+
* the patterns: a snapshot's `add -A` skips it, so neither a take-back nor a revert
|
|
127
|
+
* could tell what it holds. A write that cannot fit refuses too, with nothing
|
|
128
|
+
* written. Afterwards every written path is proven by content, the way `git status`
|
|
129
|
+
* compares it (Git's clean conversion through an index), not by a stat refresh.
|
|
130
|
+
* Its temporary index's scratch goes last, never replacing the outcome.
|
|
131
|
+
*/
|
|
132
|
+
export function materializeTree(input) {
|
|
133
|
+
return withDisposableScratch((held) => writeTree(input, held));
|
|
134
|
+
}
|
|
135
|
+
async function writeTree(input, held) {
|
|
136
|
+
const env = { GIT_OPTIONAL_LOCKS: "0" };
|
|
137
|
+
// A checkout writes files without stdout: disk activity is its progress.
|
|
138
|
+
const progress = diskProgress(input.worktree);
|
|
139
|
+
// The real index keeps the user's form, but no stat shortcut decides for it either.
|
|
140
|
+
const git = (args, label, extra = {}) => gitObserved(input.worktree, [...(held.dir ? SCRATCH_INDEX_CONFIG : STAT_TRUST_OFF), ...args], {
|
|
141
|
+
env,
|
|
142
|
+
label,
|
|
143
|
+
progress,
|
|
144
|
+
...extra,
|
|
145
|
+
});
|
|
146
|
+
let records;
|
|
147
|
+
let paths;
|
|
148
|
+
let writtenBytes;
|
|
149
|
+
const before = new Map();
|
|
150
|
+
try {
|
|
151
|
+
if (input.index === "temporary")
|
|
152
|
+
held.dir = mkdtempSync(join(tmpdir(), "claudexor-materialize-"));
|
|
153
|
+
records = parseRawRecords((await git(["diff-tree", "-r", "--raw", "-z", "--no-renames", "--no-abbrev", input.from, input.to], "diff-tree --raw")).stdout);
|
|
154
|
+
if (records.length === 0)
|
|
155
|
+
return { ok: true, changed: 0, treeMutated: false };
|
|
156
|
+
paths = records.map((record) => record.src);
|
|
157
|
+
const blocked = occupiedRefusal(input.worktree, records) ??
|
|
158
|
+
(await sparseCheckoutRefusal(input.worktree, paths, "delivery", env));
|
|
159
|
+
if (blocked)
|
|
160
|
+
return { ok: false, treeMutated: false, refusal: blocked };
|
|
161
|
+
writtenBytes = await assertWriteFits(input.worktree, records, git);
|
|
162
|
+
if (held.dir)
|
|
163
|
+
await seedScratchIndex(input, held.dir, records, env, git);
|
|
164
|
+
// The real index keeps the user's assume-unchanged marks, so its refresh
|
|
165
|
+
// compares an entry whose stat moved by content, mark or not (the checkout
|
|
166
|
+
// stat-checks a marked entry it replaces): an unchanged file is not "not uptodate".
|
|
167
|
+
// Git drops the mark of an entry whose file changed, a local edit the user keeps
|
|
168
|
+
// out of commits: those marks are put back. A marked name stdin cannot carry
|
|
169
|
+
// back keeps the plain refresh, where a stale mark refuses before writing.
|
|
170
|
+
const marks = held.dir ? null : await markedEntries(input.worktree, env);
|
|
171
|
+
const refresh = marks && marks.unnamed === 0 ? "--really-refresh" : "--refresh";
|
|
172
|
+
await git(["update-index", "-q", refresh], `update-index ${refresh}`, {
|
|
173
|
+
allowFailure: true,
|
|
174
|
+
stallMs: proofStallMs(diskBytes(input.worktree, paths)),
|
|
175
|
+
});
|
|
176
|
+
if (refresh === "--really-refresh" && marks && marks.assumed.length > 0)
|
|
177
|
+
await git(["update-index", "--assume-unchanged", "-z", "--stdin"], "update-index --assume-unchanged", { input: marks.assumed.map((path) => `${path}\0`).join("") });
|
|
178
|
+
for (const path of paths)
|
|
179
|
+
before.set(path, stamp(input.worktree, path));
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const fault = gitOrFilesystemFault(error);
|
|
183
|
+
if (!fault)
|
|
184
|
+
throw error;
|
|
185
|
+
// Nothing has been written yet: the fault is the whole answer.
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
treeMutated: false,
|
|
189
|
+
refusal: refusal(gitFaultRefusalCode(fault.code), fault.message),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
let moved;
|
|
193
|
+
try {
|
|
194
|
+
moved = await git(["read-tree", "--no-sparse-checkout", "-m", "-u", input.from, input.to], "read-tree -m -u", { allowFailure: true, stallMs: proofStallMs(writtenBytes) });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (!(error instanceof GitOperationError))
|
|
198
|
+
throw error;
|
|
199
|
+
// A stalled or killed checkout: part of `to` may have reached the target.
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
treeMutated: true,
|
|
203
|
+
refusal: refusal(gitFaultRefusalCode(error.code), error.message, paths),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (moved.code !== 0) {
|
|
207
|
+
const stderr = moved.stderr.trim();
|
|
208
|
+
const wrote = await wroteAny(input, before, env);
|
|
209
|
+
if (!wrote && REFUSED_BEFORE_WRITE.test(stderr))
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
treeMutated: false,
|
|
213
|
+
refusal: refusal("delivery_target_changed", `the target changed under paths this operation writes: ${stderr}`, paths),
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
treeMutated: wrote,
|
|
218
|
+
refusal: refusal(gitFaultRefusalCode(classifyGitFailure(stderr)), `${wrote ? "the checkout stopped partway" : "the checkout failed before writing"}: ${stderr}`, paths),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
let mismatched;
|
|
222
|
+
try {
|
|
223
|
+
mismatched = await contentMismatches(input.worktree, records, env);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const fault = gitOrFilesystemFault(error);
|
|
227
|
+
if (!fault)
|
|
228
|
+
throw error;
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
treeMutated: true,
|
|
232
|
+
unproven: paths,
|
|
233
|
+
refusal: refusal(gitFaultRefusalCode(fault.code), `the written paths could not be proven: ${fault.message}`, paths),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (mismatched.length > 0) {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
treeMutated: true,
|
|
240
|
+
unproven: mismatched,
|
|
241
|
+
refusal: refusal("delivery_content_mismatch", "written paths do not hold the delivered content after checkout conversion", mismatched),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return { ok: true, changed: records.length, treeMutated: true };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Point `GIT_INDEX_FILE` at a scratch index holding `from` that keeps the stat
|
|
248
|
+
* cache of the user's index, so the refresh before the checkout costs what
|
|
249
|
+
* `git status` costs instead of re-hashing every tracked file. The user's
|
|
250
|
+
* index path is resolved first (`--git-path index` honours `GIT_INDEX_FILE`);
|
|
251
|
+
* only that file is read, never its lock, and it is copied whole under a
|
|
252
|
+
* private name before use. A user's index that cannot seed (none yet,
|
|
253
|
+
* unmerged, unreadable) leaves a plain read of `from`. The paths this checkout
|
|
254
|
+
* replaces or removes are then re-added without stat data, so the refresh
|
|
255
|
+
* compares exactly those by content through the target's own conversion, as
|
|
256
|
+
* an index without any stat cache did: a path that does not hold `from` stays
|
|
257
|
+
* "not uptodate" and the checkout refuses before writing. A re-added entry carries
|
|
258
|
+
* none of the user's assume-unchanged or skip-worktree marks, and the scratch
|
|
259
|
+
* trusts no stat shortcut (`SCRATCH_INDEX_CONFIG`): with `core.ignoreStat` every
|
|
260
|
+
* re-added entry would be assume-valid, its content never compared.
|
|
261
|
+
*/
|
|
262
|
+
async function seedScratchIndex(input, scratch, records, env, git) {
|
|
263
|
+
const located = await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], "rev-parse index", { allowFailure: true });
|
|
264
|
+
const userIndex = located.code === 0 ? located.stdout.trim() : "";
|
|
265
|
+
const scratchIndex = join(scratch, "index");
|
|
266
|
+
env["GIT_INDEX_FILE"] = scratchIndex;
|
|
267
|
+
let seeded = false;
|
|
268
|
+
if (userIndex && existsSync(userIndex)) {
|
|
269
|
+
try {
|
|
270
|
+
copyFileSync(userIndex, `${scratchIndex}.copy`);
|
|
271
|
+
renameSync(`${scratchIndex}.copy`, scratchIndex);
|
|
272
|
+
seeded =
|
|
273
|
+
(await git(["read-tree", "-m", input.from], "read-tree -m (seed)", { allowFailure: true }))
|
|
274
|
+
.code === 0;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
seeded = false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!seeded) {
|
|
281
|
+
rmSync(scratchIndex, { force: true });
|
|
282
|
+
await git(["read-tree", input.from], "read-tree (seed)");
|
|
283
|
+
}
|
|
284
|
+
const replaced = records.filter((record) => record.oldMode !== "000000");
|
|
285
|
+
if (replaced.length > 0)
|
|
286
|
+
await git(["update-index", "-z", "--index-info"], "update-index --index-info", {
|
|
287
|
+
input: replaced
|
|
288
|
+
.map((record) => `${record.oldMode} ${record.oldOid}\t${record.src}\0`)
|
|
289
|
+
.join(""),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* After a failed checkout: did it write anything? Every path it would write
|
|
294
|
+
* must be as lstat saw it just before (type, mode, size, inode, times), and a
|
|
295
|
+
* real index must still be `from`. Nothing is re-hashed: a filter that does not
|
|
296
|
+
* round-trip would make an untouched file look written. A measurement that
|
|
297
|
+
* cannot run counts as written.
|
|
298
|
+
*/
|
|
299
|
+
async function wroteAny(input, before, env) {
|
|
300
|
+
try {
|
|
301
|
+
for (const [path, was] of before)
|
|
302
|
+
if (stamp(input.worktree, path) !== was)
|
|
303
|
+
return true;
|
|
304
|
+
if (input.index === "temporary")
|
|
305
|
+
return false;
|
|
306
|
+
const index = await gitObserved(input.worktree, ["write-tree"], { env, label: "write-tree" });
|
|
307
|
+
return index.stdout.trim() !== input.from;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Written paths whose content differs from their entries, compared as `git status`
|
|
315
|
+
* compares them: with the blob in the index, so a committed CRLF blob keeps its CR
|
|
316
|
+
* (`hash-object` loads no index and would call it changed). A private copy of the
|
|
317
|
+
* index the checkout just moved keeps every other path's stat cache; the written
|
|
318
|
+
* paths are re-added without stat data or the user's marks, so the refresh reads
|
|
319
|
+
* exactly those, whatever `core.ignoreStat` or a filesystem monitor would answer
|
|
320
|
+
* (`SCRATCH_INDEX_CONFIG`). A filter that does not round-trip still reads as different.
|
|
321
|
+
*/
|
|
322
|
+
async function indexProofMismatches(worktree, records, env, bytes) {
|
|
323
|
+
if (records.length === 0)
|
|
324
|
+
return [];
|
|
325
|
+
const located = await gitObserved(worktree, ["rev-parse", "--path-format=absolute", "--git-path", "index"], { env, label: "rev-parse index (proof)", allowFailure: true });
|
|
326
|
+
const moved = located.code === 0 ? located.stdout.trim() : "";
|
|
327
|
+
const scratch = mkdtempSync(join(tmpdir(), "claudexor-proof-"));
|
|
328
|
+
const proofEnv = { ...env, GIT_INDEX_FILE: join(scratch, "index") };
|
|
329
|
+
const git = (args, label, extra = {}) => gitObserved(worktree, [...SCRATCH_INDEX_CONFIG, ...args], {
|
|
330
|
+
env: proofEnv,
|
|
331
|
+
label,
|
|
332
|
+
...extra,
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
if (moved && existsSync(moved))
|
|
336
|
+
copyFileSync(moved, proofEnv.GIT_INDEX_FILE);
|
|
337
|
+
await git(["update-index", "--add", "-z", "--index-info"], "update-index --index-info (proof)", {
|
|
338
|
+
input: records
|
|
339
|
+
.map((record) => `${record.newMode} ${record.newOid}\t${record.src}\0`)
|
|
340
|
+
.join(""),
|
|
341
|
+
});
|
|
342
|
+
await git(["update-index", "-q", "--refresh"], "update-index --refresh (proof)", {
|
|
343
|
+
stallMs: proofStallMs(bytes),
|
|
344
|
+
});
|
|
345
|
+
const listed = await git(["diff-files", "--name-only", "-z"], "diff-files (proof)");
|
|
346
|
+
const different = new Set(listed.stdout.split("\0").filter(Boolean));
|
|
347
|
+
return records.filter((record) => different.has(record.src)).map((record) => record.src);
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* One `--stdin-paths` line naming `path` exactly. Git reads a line that starts
|
|
355
|
+
* with `"` as a C-quoted name and strips a trailing CR, so such names (and any
|
|
356
|
+
* with control characters) are quoted the way Git's unquote_c_style reads them;
|
|
357
|
+
* every other name, backslashes included, is taken verbatim.
|
|
358
|
+
*/
|
|
359
|
+
function stdinPathLine(path) {
|
|
360
|
+
if (!/^"|[\x00-\x1f\x7f]/.test(path))
|
|
361
|
+
return path;
|
|
362
|
+
const escapes = {
|
|
363
|
+
'"': '\\"',
|
|
364
|
+
"\\": "\\\\",
|
|
365
|
+
"\n": "\\n",
|
|
366
|
+
"\r": "\\r",
|
|
367
|
+
"\t": "\\t",
|
|
368
|
+
};
|
|
369
|
+
let quoted = '"';
|
|
370
|
+
for (const char of path) {
|
|
371
|
+
if (escapes[char])
|
|
372
|
+
quoted += escapes[char];
|
|
373
|
+
else if (/[\x00-\x1f\x7f]/.test(char))
|
|
374
|
+
quoted += `\\${char.charCodeAt(0).toString(8).padStart(3, "0")}`;
|
|
375
|
+
else
|
|
376
|
+
quoted += char;
|
|
377
|
+
}
|
|
378
|
+
return `${quoted}"`;
|
|
379
|
+
}
|
|
380
|
+
/** Paths whose worktree content or mode differs from the tree just written; a path
|
|
381
|
+
* that cannot be looked at throws, and its caller refuses the write as unproven. */
|
|
382
|
+
async function contentMismatches(worktree, records, env) {
|
|
383
|
+
const mismatched = [];
|
|
384
|
+
const hashed = [];
|
|
385
|
+
let hashedBytes = 0;
|
|
386
|
+
// Symlinks checked out as plain files holding their target (core.symlinks=false).
|
|
387
|
+
const linkFiles = [];
|
|
388
|
+
// On a filesystem that folds case, a removed path can read as the file written
|
|
389
|
+
// under its other spelling: one entry, not a survivor (a hard link is no alias).
|
|
390
|
+
const removedThere = [];
|
|
391
|
+
const boolConfig = async (key) => (await gitObserved(worktree, ["config", "--bool", key], {
|
|
392
|
+
env,
|
|
393
|
+
label: `config ${key}`,
|
|
394
|
+
allowFailure: true,
|
|
395
|
+
})).stdout.trim() !== "false";
|
|
396
|
+
const fileMode = await boolConfig("core.fileMode");
|
|
397
|
+
const symlinks = await boolConfig("core.symlinks");
|
|
398
|
+
for (const record of records) {
|
|
399
|
+
const path = join(worktree, record.src);
|
|
400
|
+
// Only ENOENT and ENOTDIR say a path is gone: any other error (an I/O error, a
|
|
401
|
+
// parent that can no longer be searched) leaves the write unproven, never a
|
|
402
|
+
// removal proven by not looking.
|
|
403
|
+
const stat = lstatPresent(path);
|
|
404
|
+
if (record.status === "D") {
|
|
405
|
+
if (stat && !stat.isDirectory())
|
|
406
|
+
removedThere.push(record.src);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (record.newMode === "160000")
|
|
410
|
+
continue; // a gitlink is another repository's commit
|
|
411
|
+
if (!stat) {
|
|
412
|
+
mismatched.push(record.src);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (record.newMode === "120000") {
|
|
416
|
+
if (!symlinks) {
|
|
417
|
+
// Git writes the link target's exact bytes, unconverted, as a plain file.
|
|
418
|
+
if (stat.isFile())
|
|
419
|
+
linkFiles.push(record);
|
|
420
|
+
else
|
|
421
|
+
mismatched.push(record.src);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const blob = await gitObserved(worktree, ["cat-file", "blob", record.newOid], {
|
|
425
|
+
env,
|
|
426
|
+
label: "cat-file blob",
|
|
427
|
+
});
|
|
428
|
+
if (!stat.isSymbolicLink() || readlinkSync(path) !== blob.stdout)
|
|
429
|
+
mismatched.push(record.src);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (!stat.isFile()) {
|
|
433
|
+
mismatched.push(record.src);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (fileMode && ((stat.mode & 0o100) !== 0) !== (record.newMode === "100755")) {
|
|
437
|
+
mismatched.push(record.src);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
hashed.push(record);
|
|
441
|
+
hashedBytes += stat.size;
|
|
442
|
+
}
|
|
443
|
+
if (removedThere.length > 0) {
|
|
444
|
+
const judge = aliasJudge(worktree);
|
|
445
|
+
const written = byFold(records.filter((record) => record.status !== "D").map((r) => r.src));
|
|
446
|
+
for (const path of removedThere)
|
|
447
|
+
if (!foldsOnto(judge, written, path))
|
|
448
|
+
mismatched.push(path);
|
|
449
|
+
}
|
|
450
|
+
mismatched.push(...(await indexProofMismatches(worktree, hashed, env, hashedBytes)));
|
|
451
|
+
// Link files hold their target's exact bytes, unconverted, one line each on stdin.
|
|
452
|
+
for (let index = 0; index < linkFiles.length; index += 500) {
|
|
453
|
+
const chunk = linkFiles.slice(index, index + 500);
|
|
454
|
+
const out = await gitObserved(worktree, ["hash-object", "--no-filters", "--stdin-paths"], {
|
|
455
|
+
env,
|
|
456
|
+
label: "hash-object --stdin-paths",
|
|
457
|
+
input: `${chunk.map((record) => stdinPathLine(record.src)).join("\n")}\n`,
|
|
458
|
+
});
|
|
459
|
+
const oids = out.stdout.trim().split("\n");
|
|
460
|
+
chunk.forEach((record, at) => {
|
|
461
|
+
if (oids[at] !== record.newOid)
|
|
462
|
+
mismatched.push(record.src);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return mismatched;
|
|
466
|
+
}
|
|
467
|
+
//# sourceMappingURL=object-materialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-materialize.js","sourceRoot":"","sources":["../src/object-materialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RawRecord } from "./object-compose.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether two tree paths name one directory entry through the filesystem's folding
|
|
4
|
+
* of their spelling: they differ only by case, lstat finds one inode for both, and
|
|
5
|
+
* they are not both spelled exactly as entries on disk. A hard link is an entry of
|
|
6
|
+
* its own, spelled exactly, so two of its names never count as one. Directory
|
|
7
|
+
* listings are read once, and only for such a prospective alias.
|
|
8
|
+
*/
|
|
9
|
+
export declare function aliasJudge(worktree: string): (path: string, other: string) => boolean;
|
|
10
|
+
/** Paths keyed by their folded spelling. */
|
|
11
|
+
export declare function byFold(paths: Iterable<string>): Map<string, string[]>;
|
|
12
|
+
/** Whether `path` is one of `paths` under another spelling (see {@link aliasJudge}). */
|
|
13
|
+
export declare function foldsOnto(judge: (path: string, other: string) => boolean, folded: ReadonlyMap<string, readonly string[]>, path: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* What a checkout from one tree to another would destroy without Git knowing:
|
|
16
|
+
* `read-tree -m -u` writes a path the first tree does not name over whatever is
|
|
17
|
+
* there, and removes a file or symlink standing where it needs a directory. A
|
|
18
|
+
* snapshot leaves ignored files out of that tree, so such a file is the user's and
|
|
19
|
+
* no revert could bring it back. For each added path, one lstat of it and of each
|
|
20
|
+
* parent it does not share with another; a directory holding what stays is only
|
|
21
|
+
* walked when a file is about to take its name. A directory the path merely enters
|
|
22
|
+
* loses nothing, and neither do the entries the same checkout removes. Only ENOENT
|
|
23
|
+
* and ENOTDIR are absence: a path that cannot be looked at is thrown, so the write
|
|
24
|
+
* and its dry run both refuse instead of reading it as free.
|
|
25
|
+
*/
|
|
26
|
+
export declare function occupiedPaths(worktree: string, records: readonly RawRecord[]): string[];
|
|
27
|
+
//# sourceMappingURL=object-occupied.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-occupied.d.ts","sourceRoot":"","sources":["../src/object-occupied.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAgBrD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAuBrF;AAED,4CAA4C;AAC5C,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAIrE;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,EAC/C,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,EAC9C,IAAI,EAAE,MAAM,GACX,OAAO,CAET;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,EAAE,CAsCvF"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { lstatPresent } from "./fs-presence.js";
|
|
4
|
+
const ABSENT_MODE = "000000";
|
|
5
|
+
/** For the alias judge only: a path it cannot look at is no alias (the exemption is denied). */
|
|
6
|
+
const lstatOrNull = (path) => {
|
|
7
|
+
try {
|
|
8
|
+
return lstatPresent(path);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/** A name as a volume that folds case reads it (normalization included). */
|
|
15
|
+
const fold = (path) => path.normalize("NFC").toLowerCase();
|
|
16
|
+
/**
|
|
17
|
+
* Whether two tree paths name one directory entry through the filesystem's folding
|
|
18
|
+
* of their spelling: they differ only by case, lstat finds one inode for both, and
|
|
19
|
+
* they are not both spelled exactly as entries on disk. A hard link is an entry of
|
|
20
|
+
* its own, spelled exactly, so two of its names never count as one. Directory
|
|
21
|
+
* listings are read once, and only for such a prospective alias.
|
|
22
|
+
*/
|
|
23
|
+
export function aliasJudge(worktree) {
|
|
24
|
+
const listings = new Map();
|
|
25
|
+
const listing = (dir) => {
|
|
26
|
+
if (!listings.has(dir)) {
|
|
27
|
+
try {
|
|
28
|
+
listings.set(dir, new Set(readdirSync(join(worktree, dir))));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
listings.set(dir, null);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return listings.get(dir);
|
|
35
|
+
};
|
|
36
|
+
const spelledExactly = (path) => {
|
|
37
|
+
const parts = path.split("/");
|
|
38
|
+
return parts.every((part, at) => listing(parts.slice(0, at).join("/"))?.has(part) === true);
|
|
39
|
+
};
|
|
40
|
+
return (path, other) => {
|
|
41
|
+
if (path === other || fold(path) !== fold(other))
|
|
42
|
+
return false;
|
|
43
|
+
const one = lstatOrNull(join(worktree, path));
|
|
44
|
+
const two = lstatOrNull(join(worktree, other));
|
|
45
|
+
if (!one || !two || one.dev !== two.dev || one.ino !== two.ino)
|
|
46
|
+
return false;
|
|
47
|
+
return !(spelledExactly(path) && spelledExactly(other));
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Paths keyed by their folded spelling. */
|
|
51
|
+
export function byFold(paths) {
|
|
52
|
+
const map = new Map();
|
|
53
|
+
for (const path of paths)
|
|
54
|
+
map.set(fold(path), [...(map.get(fold(path)) ?? []), path]);
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
/** Whether `path` is one of `paths` under another spelling (see {@link aliasJudge}). */
|
|
58
|
+
export function foldsOnto(judge, folded, path) {
|
|
59
|
+
return (folded.get(fold(path)) ?? []).some((other) => judge(path, other));
|
|
60
|
+
}
|
|
61
|
+
/** What a directory holds that is not on its way out, as tree paths (empty ones hold nothing). */
|
|
62
|
+
function survivors(worktree, dir, removed) {
|
|
63
|
+
const found = [];
|
|
64
|
+
for (const entry of readdirSync(join(worktree, dir), { withFileTypes: true })) {
|
|
65
|
+
const path = `${dir}/${entry.name}`;
|
|
66
|
+
if (entry.isDirectory())
|
|
67
|
+
found.push(...survivors(worktree, path, removed));
|
|
68
|
+
else if (!removed.has(path))
|
|
69
|
+
found.push(path);
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* What a checkout from one tree to another would destroy without Git knowing:
|
|
75
|
+
* `read-tree -m -u` writes a path the first tree does not name over whatever is
|
|
76
|
+
* there, and removes a file or symlink standing where it needs a directory. A
|
|
77
|
+
* snapshot leaves ignored files out of that tree, so such a file is the user's and
|
|
78
|
+
* no revert could bring it back. For each added path, one lstat of it and of each
|
|
79
|
+
* parent it does not share with another; a directory holding what stays is only
|
|
80
|
+
* walked when a file is about to take its name. A directory the path merely enters
|
|
81
|
+
* loses nothing, and neither do the entries the same checkout removes. Only ENOENT
|
|
82
|
+
* and ENOTDIR are absence: a path that cannot be looked at is thrown, so the write
|
|
83
|
+
* and its dry run both refuse instead of reading it as free.
|
|
84
|
+
*/
|
|
85
|
+
export function occupiedPaths(worktree, records) {
|
|
86
|
+
const removed = new Set(records.filter((record) => record.newMode === ABSENT_MODE).map((record) => record.src));
|
|
87
|
+
// Parents already looked at: a real directory is entered; anything else stops the walk.
|
|
88
|
+
const parents = new Map();
|
|
89
|
+
const occupied = new Set();
|
|
90
|
+
/** On a filesystem that folds case, a path about to go can answer for a new spelling. */
|
|
91
|
+
const judge = aliasJudge(worktree);
|
|
92
|
+
const leaving = byFold(removed);
|
|
93
|
+
const isLeaving = (path) => foldsOnto(judge, leaving, path);
|
|
94
|
+
const enterable = (parent) => {
|
|
95
|
+
const known = parents.get(parent);
|
|
96
|
+
if (known)
|
|
97
|
+
return known === "enter";
|
|
98
|
+
const cut = parent.lastIndexOf("/");
|
|
99
|
+
const reachable = cut <= 0 || enterable(parent.slice(0, cut));
|
|
100
|
+
let answer = "stop";
|
|
101
|
+
if (reachable) {
|
|
102
|
+
const stat = lstatPresent(join(worktree, parent));
|
|
103
|
+
if (stat?.isDirectory())
|
|
104
|
+
answer = "enter";
|
|
105
|
+
else if (stat && !removed.has(parent) && !isLeaving(parent))
|
|
106
|
+
occupied.add(parent);
|
|
107
|
+
}
|
|
108
|
+
parents.set(parent, answer);
|
|
109
|
+
return answer === "enter";
|
|
110
|
+
};
|
|
111
|
+
for (const record of records) {
|
|
112
|
+
if (record.oldMode !== ABSENT_MODE)
|
|
113
|
+
continue;
|
|
114
|
+
const cut = record.src.lastIndexOf("/");
|
|
115
|
+
if (cut > 0 && !enterable(record.src.slice(0, cut)))
|
|
116
|
+
continue;
|
|
117
|
+
const stat = lstatPresent(join(worktree, record.src));
|
|
118
|
+
if (!stat)
|
|
119
|
+
continue;
|
|
120
|
+
if (stat.isDirectory()) {
|
|
121
|
+
// A gitlink's own directory stays; a file taking its name would remove it.
|
|
122
|
+
if (record.newMode !== "160000")
|
|
123
|
+
for (const path of survivors(worktree, record.src, removed))
|
|
124
|
+
occupied.add(path);
|
|
125
|
+
}
|
|
126
|
+
else if (!isLeaving(record.src))
|
|
127
|
+
occupied.add(record.src);
|
|
128
|
+
}
|
|
129
|
+
return [...occupied];
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=object-occupied.js.map
|