@ricsam/r5d-worker 0.0.78 → 0.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cjs/main.cjs +1550 -335
- package/dist/cjs/managed-paths.cjs +101 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-workspace-state.cjs +184 -5
- package/dist/cjs/project-worktrees.cjs +641 -56
- package/dist/cjs/registry-auth.cjs +310 -0
- package/dist/cjs/repository-transition-policy.cjs +49 -0
- package/dist/cjs/supervisor.cjs +62 -11
- package/dist/cjs/working-tree-mirror.cjs +191 -34
- package/dist/cjs/workspace-automatic-sync-policy.cjs +69 -0
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +37 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +18 -0
- package/dist/cjs/workspace-git-sync.cjs +1028 -54
- package/dist/cjs/workspace-mount-boundary.cjs +71 -0
- package/dist/cjs/workspace-path-move.cjs +195 -0
- package/dist/cjs/workspace-preserve-only-policy.cjs +36 -0
- package/dist/cjs/workspace-project-config-policy.cjs +46 -0
- package/dist/cjs/workspace-publication-evidence.cjs +46 -0
- package/dist/mjs/main.mjs +1584 -341
- package/dist/mjs/managed-paths.mjs +100 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-workspace-state.mjs +181 -5
- package/dist/mjs/project-worktrees.mjs +632 -55
- package/dist/mjs/registry-auth.mjs +269 -0
- package/dist/mjs/repository-transition-policy.mjs +22 -0
- package/dist/mjs/supervisor.mjs +61 -11
- package/dist/mjs/working-tree-mirror.mjs +190 -34
- package/dist/mjs/workspace-automatic-sync-policy.mjs +40 -0
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +13 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +17 -0
- package/dist/mjs/workspace-git-sync.mjs +1023 -54
- package/dist/mjs/workspace-mount-boundary.mjs +37 -0
- package/dist/mjs/workspace-path-move.mjs +160 -0
- package/dist/mjs/workspace-preserve-only-policy.mjs +11 -0
- package/dist/mjs/workspace-project-config-policy.mjs +21 -0
- package/dist/mjs/workspace-publication-evidence.mjs +21 -0
- package/dist/types/credential-authority-lock-fixture.d.ts +1 -0
- package/dist/types/main.d.ts +175 -1
- package/dist/types/managed-paths.d.ts +11 -0
- package/dist/types/project-workspace-state.d.ts +45 -1
- package/dist/types/project-worktrees.d.ts +119 -2
- package/dist/types/registry-auth.d.ts +47 -0
- package/dist/types/repository-transition-policy.d.ts +26 -0
- package/dist/types/supervisor-daemonized-fixture.d.ts +1 -0
- package/dist/types/supervisor-signal-fixture.d.ts +1 -0
- package/dist/types/supervisor.d.ts +6 -4
- package/dist/types/working-tree-mirror.d.ts +7 -0
- package/dist/types/workspace-automatic-sync-policy.d.ts +37 -0
- package/dist/types/workspace-branch-incarnation-policy.d.ts +14 -0
- package/dist/types/workspace-command-sync-policy.d.ts +9 -0
- package/dist/types/workspace-git-sync.d.ts +33 -3
- package/dist/types/workspace-mount-boundary.d.ts +10 -0
- package/dist/types/workspace-path-move.d.ts +21 -0
- package/dist/types/workspace-preserve-only-policy.d.ts +15 -0
- package/dist/types/workspace-project-config-policy.d.ts +34 -0
- package/dist/types/workspace-publication-evidence.d.ts +17 -0
- package/package.json +1 -1
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { workerGitProcessEnvironment } from "./git-process-environment.mjs";
|
|
5
|
+
const HFS_IGNORED_CODE_POINTS = /[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/gu;
|
|
6
|
+
const MIRROR_TEMPORARY_FILE_PREFIX = ".r5d-working-tree-mirror-";
|
|
7
|
+
function isPortableGitMetadataSegment(segment) {
|
|
8
|
+
const hfsFolded = segment.replace(HFS_IGNORED_CODE_POINTS, "").toLowerCase();
|
|
9
|
+
if (hfsFolded === ".git") return true;
|
|
10
|
+
const ntfsFolded = segment.toLowerCase();
|
|
11
|
+
return /^(?:\.git|git~1)[ .]*(?::|$)/u.test(ntfsFolded);
|
|
12
|
+
}
|
|
4
13
|
function isGitMetadataPath(relativePath) {
|
|
5
|
-
return relativePath.split(
|
|
14
|
+
return relativePath.split(/[\\/]/u).some(isPortableGitMetadataSegment);
|
|
15
|
+
}
|
|
16
|
+
function assertWorkingTreeHasNoPortableGitMetadataAliases(root) {
|
|
17
|
+
root = path.resolve(root);
|
|
18
|
+
const rootStatus = fs.lstatSync(root);
|
|
19
|
+
if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
|
|
20
|
+
throw new Error(`Working-tree root is not a regular directory: ${root}`);
|
|
21
|
+
}
|
|
22
|
+
const inspectDirectory = (directoryPath, relativeDirectory) => {
|
|
23
|
+
for (const name of fs.readdirSync(directoryPath).sort()) {
|
|
24
|
+
const relativePath = relativeDirectory ? path.posix.join(relativeDirectory, name) : name;
|
|
25
|
+
if (name === ".git") continue;
|
|
26
|
+
if (isGitMetadataPath(relativePath)) {
|
|
27
|
+
throw new Error(`Working tree contains a portable Git metadata alias that cannot be moved safely: ${relativePath}`);
|
|
28
|
+
}
|
|
29
|
+
const entryPath = path.join(directoryPath, name);
|
|
30
|
+
const status = fs.lstatSync(entryPath);
|
|
31
|
+
if (status.isDirectory() && !status.isSymbolicLink()) inspectDirectory(entryPath, relativePath);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
inspectDirectory(root, "");
|
|
6
35
|
}
|
|
7
36
|
function normalizeRelativePath(value) {
|
|
8
37
|
const normalized = value.split(path.sep).join(path.posix.sep).replace(/^\.\/+/, "").replace(/^\/+|\/+$/g, "");
|
|
@@ -15,6 +44,41 @@ function assertInside(root, candidate) {
|
|
|
15
44
|
throw new Error(`Working-tree path escapes its root: ${candidate}`);
|
|
16
45
|
}
|
|
17
46
|
}
|
|
47
|
+
function inspectRelativePathWithoutFollowingAncestors(root, relativePath) {
|
|
48
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
49
|
+
if (!normalized) return { kind: "blocked", blockingPath: relativePath };
|
|
50
|
+
let current = path.resolve(root);
|
|
51
|
+
const segments = normalized.split("/");
|
|
52
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
53
|
+
current = path.join(current, segments[index]);
|
|
54
|
+
assertInside(root, current);
|
|
55
|
+
let stat;
|
|
56
|
+
try {
|
|
57
|
+
stat = fs.lstatSync(current);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
if (index < segments.length - 1 && (!stat.isDirectory() || stat.isSymbolicLink())) {
|
|
63
|
+
return { kind: "blocked", blockingPath: current };
|
|
64
|
+
}
|
|
65
|
+
if (index === segments.length - 1) return { kind: "entry", absolutePath: current, stat };
|
|
66
|
+
}
|
|
67
|
+
return { kind: "missing" };
|
|
68
|
+
}
|
|
69
|
+
function inspectDirectoryRoot(root, options) {
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = fs.lstatSync(root);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (options.allowMissing && error.code === "ENOENT") return false;
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
78
|
+
throw new Error(`${options.label} is not a regular directory: ${root}`);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
18
82
|
function gitEligibleRoots(root) {
|
|
19
83
|
const result = Bun.spawnSync(
|
|
20
84
|
[
|
|
@@ -51,16 +115,13 @@ function gitEligibleRoots(root) {
|
|
|
51
115
|
function addEntry(entries, root, relativePath, recurseDirectories) {
|
|
52
116
|
const normalized = normalizeRelativePath(relativePath);
|
|
53
117
|
if (!normalized || entries.has(normalized)) return;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
stat = fs.lstatSync(absolutePath);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
if (error.code === "ENOENT") return;
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
118
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(root, normalized);
|
|
119
|
+
if (inspected.kind !== "entry") return;
|
|
120
|
+
const { absolutePath, stat } = inspected;
|
|
63
121
|
if (stat.isSymbolicLink()) {
|
|
122
|
+
for (const existing of [...entries.keys()]) {
|
|
123
|
+
if (existing.startsWith(`${normalized}/`)) entries.delete(existing);
|
|
124
|
+
}
|
|
64
125
|
entries.set(normalized, { kind: "symlink", mode: stat.mode, target: fs.readlinkSync(absolutePath) });
|
|
65
126
|
return;
|
|
66
127
|
}
|
|
@@ -80,9 +141,11 @@ function addParentDirectories(entries, root) {
|
|
|
80
141
|
let parent = path.posix.dirname(relativePath);
|
|
81
142
|
while (parent !== ".") {
|
|
82
143
|
if (!entries.has(parent)) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
144
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(root, parent);
|
|
145
|
+
if (inspected.kind !== "entry" || !inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) {
|
|
146
|
+
throw new Error(`Working-tree entry has an unsafe parent directory: ${parent}`);
|
|
147
|
+
}
|
|
148
|
+
entries.set(parent, { kind: "directory", mode: inspected.stat.mode });
|
|
86
149
|
}
|
|
87
150
|
parent = path.posix.dirname(parent);
|
|
88
151
|
}
|
|
@@ -90,7 +153,8 @@ function addParentDirectories(entries, root) {
|
|
|
90
153
|
}
|
|
91
154
|
function inspectWorkingTree(root, mode) {
|
|
92
155
|
const entries = /* @__PURE__ */ new Map();
|
|
93
|
-
|
|
156
|
+
root = path.resolve(root);
|
|
157
|
+
if (!inspectDirectoryRoot(root, { allowMissing: true, label: "Working-tree root" })) return entries;
|
|
94
158
|
if (mode === "all") {
|
|
95
159
|
for (const child of fs.readdirSync(root).sort()) addEntry(entries, root, child, true);
|
|
96
160
|
} else {
|
|
@@ -102,32 +166,58 @@ function inspectWorkingTree(root, mode) {
|
|
|
102
166
|
return entries;
|
|
103
167
|
}
|
|
104
168
|
function removeEntry(targetRoot, relativePath) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
169
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
170
|
+
if (inspected.kind === "missing") return;
|
|
171
|
+
if (inspected.kind === "blocked") {
|
|
172
|
+
throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
|
|
173
|
+
}
|
|
174
|
+
fs.rmSync(inspected.absolutePath, { recursive: true, force: true });
|
|
108
175
|
}
|
|
109
176
|
function ensureDirectory(targetRoot, relativePath, mode) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
177
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
178
|
+
if (!normalized) throw new Error(`Invalid working-tree directory path: ${relativePath}`);
|
|
179
|
+
let current = path.resolve(targetRoot);
|
|
180
|
+
const segments = normalized.split("/");
|
|
181
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
182
|
+
current = path.join(current, segments[index]);
|
|
183
|
+
assertInside(targetRoot, current);
|
|
184
|
+
let stat = null;
|
|
185
|
+
try {
|
|
186
|
+
stat = fs.lstatSync(current);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error.code !== "ENOENT") throw error;
|
|
189
|
+
}
|
|
190
|
+
if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) {
|
|
191
|
+
fs.rmSync(current, { recursive: true, force: true });
|
|
192
|
+
stat = null;
|
|
193
|
+
}
|
|
194
|
+
if (!stat) fs.mkdirSync(current, { mode: index === segments.length - 1 ? mode & 511 : 493 });
|
|
195
|
+
}
|
|
114
196
|
}
|
|
115
|
-
function filesEqual(leftPath, rightPath,
|
|
197
|
+
function filesEqual(leftPath, rightPath, entry) {
|
|
116
198
|
let rightStat;
|
|
117
199
|
try {
|
|
118
200
|
rightStat = fs.lstatSync(rightPath);
|
|
119
201
|
} catch {
|
|
120
202
|
return false;
|
|
121
203
|
}
|
|
122
|
-
if (!rightStat.isFile() || rightStat.size !== size) return false;
|
|
123
|
-
|
|
124
|
-
|
|
204
|
+
if (!rightStat.isFile() || rightStat.size !== entry.size || (rightStat.mode & 511) !== (entry.mode & 511)) return false;
|
|
205
|
+
let left;
|
|
206
|
+
let right;
|
|
207
|
+
try {
|
|
208
|
+
left = fs.openSync(leftPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
209
|
+
right = fs.openSync(rightPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
210
|
+
} catch {
|
|
211
|
+
if (left !== void 0) fs.closeSync(left);
|
|
212
|
+
if (right !== void 0) fs.closeSync(right);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
125
215
|
const leftBuffer = Buffer.allocUnsafe(64 * 1024);
|
|
126
216
|
const rightBuffer = Buffer.allocUnsafe(64 * 1024);
|
|
127
217
|
try {
|
|
128
218
|
let offset = 0;
|
|
129
|
-
while (offset < size) {
|
|
130
|
-
const length = Math.min(leftBuffer.length, size - offset);
|
|
219
|
+
while (offset < entry.size) {
|
|
220
|
+
const length = Math.min(leftBuffer.length, entry.size - offset);
|
|
131
221
|
const leftRead = fs.readSync(left, leftBuffer, 0, length, offset);
|
|
132
222
|
const rightRead = fs.readSync(right, rightBuffer, 0, length, offset);
|
|
133
223
|
if (leftRead !== rightRead || !leftBuffer.subarray(0, leftRead).equals(rightBuffer.subarray(0, rightRead))) return false;
|
|
@@ -139,18 +229,73 @@ function filesEqual(leftPath, rightPath, size) {
|
|
|
139
229
|
fs.closeSync(right);
|
|
140
230
|
}
|
|
141
231
|
}
|
|
232
|
+
function copyRegularFile(sourcePath, targetPath, entry) {
|
|
233
|
+
const source = fs.openSync(sourcePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
234
|
+
const temporaryPath = path.join(path.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${randomUUID()}.tmp`);
|
|
235
|
+
let target;
|
|
236
|
+
try {
|
|
237
|
+
const sourceStat = fs.fstatSync(source);
|
|
238
|
+
if (!sourceStat.isFile() || sourceStat.size !== entry.size || (sourceStat.mode & 511) !== (entry.mode & 511)) {
|
|
239
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
240
|
+
}
|
|
241
|
+
target = fs.openSync(
|
|
242
|
+
temporaryPath,
|
|
243
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW,
|
|
244
|
+
entry.mode & 511
|
|
245
|
+
);
|
|
246
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
247
|
+
let offset = 0;
|
|
248
|
+
while (offset < sourceStat.size) {
|
|
249
|
+
const bytesRead = fs.readSync(source, buffer, 0, Math.min(buffer.length, sourceStat.size - offset), offset);
|
|
250
|
+
if (bytesRead <= 0) throw new Error(`Working-tree source ended while it was being mirrored: ${sourcePath}`);
|
|
251
|
+
let written = 0;
|
|
252
|
+
while (written < bytesRead) {
|
|
253
|
+
written += fs.writeSync(target, buffer, written, bytesRead - written, offset + written);
|
|
254
|
+
}
|
|
255
|
+
offset += bytesRead;
|
|
256
|
+
}
|
|
257
|
+
fs.fchmodSync(target, entry.mode & 511);
|
|
258
|
+
fs.fsyncSync(target);
|
|
259
|
+
const finalSourceStat = fs.fstatSync(source);
|
|
260
|
+
if (!finalSourceStat.isFile() || finalSourceStat.size !== sourceStat.size || (finalSourceStat.mode & 511) !== (sourceStat.mode & 511) || finalSourceStat.mtimeMs !== sourceStat.mtimeMs || finalSourceStat.ctimeMs !== sourceStat.ctimeMs) {
|
|
261
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
262
|
+
}
|
|
263
|
+
fs.closeSync(target);
|
|
264
|
+
target = void 0;
|
|
265
|
+
fs.renameSync(temporaryPath, targetPath);
|
|
266
|
+
const parent = fs.openSync(path.dirname(targetPath), fs.constants.O_RDONLY);
|
|
267
|
+
try {
|
|
268
|
+
fs.fsyncSync(parent);
|
|
269
|
+
} finally {
|
|
270
|
+
fs.closeSync(parent);
|
|
271
|
+
}
|
|
272
|
+
} finally {
|
|
273
|
+
if (target !== void 0) fs.closeSync(target);
|
|
274
|
+
fs.closeSync(source);
|
|
275
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
142
278
|
function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
143
|
-
const sourcePath = path.join(sourceRoot, ...relativePath.split("/"));
|
|
144
279
|
const targetPath = path.join(targetRoot, ...relativePath.split("/"));
|
|
145
|
-
assertInside(sourceRoot, sourcePath);
|
|
146
280
|
assertInside(targetRoot, targetPath);
|
|
147
281
|
const parent = path.posix.dirname(relativePath);
|
|
148
282
|
if (parent !== ".") ensureDirectory(targetRoot, parent, 493);
|
|
283
|
+
const source = inspectRelativePathWithoutFollowingAncestors(sourceRoot, relativePath);
|
|
284
|
+
if (source.kind !== "entry") {
|
|
285
|
+
throw new Error(`Working-tree source became unsafe while it was being mirrored: ${relativePath}`);
|
|
286
|
+
}
|
|
287
|
+
const sourcePath = source.absolutePath;
|
|
149
288
|
if (entry.kind === "directory") {
|
|
289
|
+
if (!source.stat.isDirectory() || source.stat.isSymbolicLink()) {
|
|
290
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
291
|
+
}
|
|
150
292
|
ensureDirectory(targetRoot, relativePath, entry.mode);
|
|
151
293
|
return;
|
|
152
294
|
}
|
|
153
295
|
if (entry.kind === "symlink") {
|
|
296
|
+
if (!source.stat.isSymbolicLink() || fs.readlinkSync(sourcePath) !== entry.target) {
|
|
297
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
298
|
+
}
|
|
154
299
|
let unchanged = false;
|
|
155
300
|
try {
|
|
156
301
|
unchanged = fs.lstatSync(targetPath).isSymbolicLink() && fs.readlinkSync(targetPath) === entry.target;
|
|
@@ -163,16 +308,26 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
|
163
308
|
}
|
|
164
309
|
return;
|
|
165
310
|
}
|
|
166
|
-
if (!
|
|
167
|
-
|
|
168
|
-
|
|
311
|
+
if (!source.stat.isFile() || source.stat.isSymbolicLink() || source.stat.size !== entry.size) {
|
|
312
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
313
|
+
}
|
|
314
|
+
if (!filesEqual(sourcePath, targetPath, entry)) {
|
|
315
|
+
const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
316
|
+
if (target.kind === "blocked") {
|
|
317
|
+
throw new Error(`Refusing to copy through a symlink or non-directory: ${target.blockingPath}`);
|
|
318
|
+
}
|
|
319
|
+
const targetStat = target.kind === "entry" ? target.stat : null;
|
|
320
|
+
if (targetStat && !targetStat.isFile()) removeEntry(targetRoot, relativePath);
|
|
321
|
+
copyRegularFile(sourcePath, targetPath, entry);
|
|
169
322
|
}
|
|
170
|
-
fs.chmodSync(targetPath, entry.mode & 511);
|
|
171
323
|
}
|
|
172
324
|
function mirrorWorkingTree(input) {
|
|
173
325
|
const sourceRoot = path.resolve(input.sourceRoot);
|
|
174
326
|
const targetRoot = path.resolve(input.targetRoot);
|
|
175
|
-
|
|
327
|
+
if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
|
|
328
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
329
|
+
}
|
|
330
|
+
inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
|
|
176
331
|
const desired = inspectWorkingTree(sourceRoot, input.sourceMode);
|
|
177
332
|
const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all");
|
|
178
333
|
for (const relativePath of [...existing.keys()].sort((left, right) => right.split("/").length - left.split("/").length)) {
|
|
@@ -186,6 +341,7 @@ function mirrorWorkingTree(input) {
|
|
|
186
341
|
return { paths: [...desired.keys()].filter((relativePath) => desired.get(relativePath)?.kind !== "directory").sort() };
|
|
187
342
|
}
|
|
188
343
|
export {
|
|
344
|
+
assertWorkingTreeHasNoPortableGitMetadataAliases,
|
|
189
345
|
inspectWorkingTree,
|
|
190
346
|
mirrorWorkingTree
|
|
191
347
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS = 12e4;
|
|
2
|
+
function pendingCreatedBranchKey(projectId, branchName) {
|
|
3
|
+
return `${projectId}\0${branchName}`;
|
|
4
|
+
}
|
|
5
|
+
function hasActiveVisibleProjectsWorkspaceTarget(activeTargets) {
|
|
6
|
+
for (const target of activeTargets) {
|
|
7
|
+
if (target.type === "workspace" && target.rootProfile === "visible_projects") return true;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTargets) {
|
|
12
|
+
for (const target of activeTargets) {
|
|
13
|
+
if (target.type === "project" && target.projectId === projectId && target.branchName === branchName) return true;
|
|
14
|
+
if (target.type === "workspace" && target.rootProfile === "visible_projects") return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
function shouldDeferAutomaticWorkspaceSyncForPendingBranch(pendingCreatedBranches, activeTargets) {
|
|
19
|
+
if (pendingCreatedBranches.length === 0) return false;
|
|
20
|
+
const targets = [...activeTargets];
|
|
21
|
+
return pendingCreatedBranches.some(({ projectId, branchName }) => projectBranchHasActiveWorkspaceTarget(projectId, branchName, targets));
|
|
22
|
+
}
|
|
23
|
+
function pendingCreatedBranchAutomaticSyncDeferral(pendingCreatedBranches, activeTargets, publicationNotBeforeByBranch, nowMs = Date.now()) {
|
|
24
|
+
if (shouldDeferAutomaticWorkspaceSyncForPendingBranch(pendingCreatedBranches, activeTargets)) {
|
|
25
|
+
return { kind: "active_target" };
|
|
26
|
+
}
|
|
27
|
+
const notBeforeMs = pendingCreatedBranches.reduce(
|
|
28
|
+
(latest, { projectId, branchName }) => Math.max(latest, publicationNotBeforeByBranch.get(pendingCreatedBranchKey(projectId, branchName)) ?? 0),
|
|
29
|
+
0
|
|
30
|
+
);
|
|
31
|
+
return notBeforeMs > nowMs ? { kind: "creation_grace", retryAfterMs: notBeforeMs - nowMs } : null;
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS,
|
|
35
|
+
hasActiveVisibleProjectsWorkspaceTarget,
|
|
36
|
+
pendingCreatedBranchAutomaticSyncDeferral,
|
|
37
|
+
pendingCreatedBranchKey,
|
|
38
|
+
projectBranchHasActiveWorkspaceTarget,
|
|
39
|
+
shouldDeferAutomaticWorkspaceSyncForPendingBranch
|
|
40
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function assertProjectBranchDeletionIncarnation(input) {
|
|
2
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(input.requiredBranchId)) {
|
|
3
|
+
throw new Error("Project branch deletion requires a valid durable branch incarnation");
|
|
4
|
+
}
|
|
5
|
+
for (const branch of [input.configuredBranch, input.pendingLocalBranch]) {
|
|
6
|
+
if (branch && branch.branchId !== input.requiredBranchId) {
|
|
7
|
+
throw new Error("Project branch deletion belongs to another durable branch incarnation");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
assertProjectBranchDeletionIncarnation
|
|
13
|
+
};
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
function usesVisibleWorkspace(target) {
|
|
2
|
+
return target.type === "project" || target.rootProfile === "visible_projects";
|
|
3
|
+
}
|
|
4
|
+
async function reserveWorkspaceCommandAfterCurrentSync(target, coordinator, reserve) {
|
|
5
|
+
if (!usesVisibleWorkspace(target)) {
|
|
6
|
+
reserve();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
while (true) {
|
|
10
|
+
const currentSync = coordinator.afterCurrent();
|
|
11
|
+
await currentSync;
|
|
12
|
+
if (currentSync !== coordinator.afterCurrent()) continue;
|
|
13
|
+
reserve();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
1
17
|
function shouldSerializeWorkspaceCommand(target) {
|
|
2
18
|
return target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
3
19
|
}
|
|
@@ -9,6 +25,7 @@ function acquireWorkspaceCommandMutation(target, coordinator) {
|
|
|
9
25
|
}
|
|
10
26
|
export {
|
|
11
27
|
acquireWorkspaceCommandMutation,
|
|
28
|
+
reserveWorkspaceCommandAfterCurrentSync,
|
|
12
29
|
runWorkspaceCommand,
|
|
13
30
|
shouldSerializeWorkspaceCommand
|
|
14
31
|
};
|