@ricsam/r5d-worker 0.0.78 → 0.0.80
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 +676 -58
- 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 +196 -36
- 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 +1037 -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 +667 -57
- 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 +195 -36
- 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 +1032 -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 +14 -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
|
@@ -30,11 +30,16 @@ var workspace_git_sync_exports = {};
|
|
|
30
30
|
__export(workspace_git_sync_exports, {
|
|
31
31
|
MAX_WORKSPACE_GIT_DIFF_BYTES: () => MAX_WORKSPACE_GIT_DIFF_BYTES,
|
|
32
32
|
WORKSPACE_GIT_BRANCH: () => WORKSPACE_GIT_BRANCH,
|
|
33
|
+
WORKSPACE_GIT_CHECKOUT_DURABILITY: () => WORKSPACE_GIT_CHECKOUT_DURABILITY,
|
|
33
34
|
WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION: () => WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
|
|
35
|
+
WORKSPACE_GIT_HYDRATED_RECEIPT: () => WORKSPACE_GIT_HYDRATED_RECEIPT,
|
|
36
|
+
WORKSPACE_GIT_HYDRATION_TRANSACTION: () => WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
34
37
|
ensureWorkspaceGitClone: () => ensureWorkspaceGitClone,
|
|
35
38
|
hydrateWorkspaceGitMounts: () => hydrateWorkspaceGitMounts,
|
|
39
|
+
recoverWorkspaceGitHydration: () => recoverWorkspaceGitHydration,
|
|
36
40
|
resetWorkspaceGit: () => resetWorkspaceGit,
|
|
37
41
|
synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
|
|
42
|
+
workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
|
|
38
43
|
workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
|
|
39
44
|
});
|
|
40
45
|
module.exports = __toCommonJS(workspace_git_sync_exports);
|
|
@@ -42,10 +47,14 @@ var import_node_fs = __toESM(require("node:fs"), 1);
|
|
|
42
47
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
43
48
|
var import_git_process_environment = require("./git-process-environment.cjs");
|
|
44
49
|
var import_working_tree_mirror = require("./working-tree-mirror.cjs");
|
|
50
|
+
var import_workspace_mount_boundary = require("./workspace-mount-boundary.cjs");
|
|
45
51
|
const WORKSPACE_GIT_BRANCH = "main";
|
|
46
52
|
const MAX_WORKSPACE_GIT_DIFF_BYTES = 5 * 1024 * 1024;
|
|
47
53
|
const WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION = "r5d-confirm-large-diff-v1";
|
|
48
54
|
const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
55
|
+
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
56
|
+
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
57
|
+
const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
49
58
|
const NON_RECURSIVE_GIT_CONFIG = [
|
|
50
59
|
"-c",
|
|
51
60
|
"submodule.recurse=false",
|
|
@@ -88,6 +97,640 @@ function revParse(workspacePath, revision) {
|
|
|
88
97
|
function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
89
98
|
git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
|
|
90
99
|
}
|
|
100
|
+
function fsyncDirectory(directoryPath) {
|
|
101
|
+
const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
|
|
102
|
+
try {
|
|
103
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
104
|
+
} finally {
|
|
105
|
+
import_node_fs.default.closeSync(descriptor);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function lstatIfExists(targetPath) {
|
|
109
|
+
try {
|
|
110
|
+
return import_node_fs.default.lstatSync(targetPath);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error.code === "ENOENT") return null;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function hydratedWorkspaceReceiptPath(workspacePath) {
|
|
117
|
+
return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATED_RECEIPT.split("/"));
|
|
118
|
+
}
|
|
119
|
+
function workspaceCheckoutDurabilityPath(workspacePath) {
|
|
120
|
+
return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_CHECKOUT_DURABILITY.split("/"));
|
|
121
|
+
}
|
|
122
|
+
function normalizedWorkspaceMountPath(workspaceRelativePath) {
|
|
123
|
+
return workspaceRelativePath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
124
|
+
}
|
|
125
|
+
function workspaceHydrationMountBasis(mount) {
|
|
126
|
+
return {
|
|
127
|
+
id: mount.id,
|
|
128
|
+
incarnationKey: mount.hydrationIncarnationKey,
|
|
129
|
+
sourcePath: import_node_path.default.resolve(mount.sourcePath),
|
|
130
|
+
durabilityRootPath: import_node_path.default.resolve(mount.durabilityRootPath),
|
|
131
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
132
|
+
sourceMode: mount.sourceMode,
|
|
133
|
+
hydrateDeletionMode: mount.hydrateDeletionMode,
|
|
134
|
+
preserveLocalOnInitialOuterAbsence: mount.preserveLocalOnInitialOuterAbsence === true,
|
|
135
|
+
preserveLocalOnHydrationBasisChange: mount.preserveLocalOnHydrationBasisChange === true,
|
|
136
|
+
deleteWhenSourceMissing: mount.deleteWhenSourceMissing === true
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function compareWorkspaceHydrationMountBasis(left, right) {
|
|
140
|
+
return left.id.localeCompare(right.id);
|
|
141
|
+
}
|
|
142
|
+
function workspaceHydrationReceipt(head, mounts) {
|
|
143
|
+
const bases = mounts.map(workspaceHydrationMountBasis).sort(compareWorkspaceHydrationMountBasis);
|
|
144
|
+
const ids = /* @__PURE__ */ new Set();
|
|
145
|
+
for (const basis of bases) {
|
|
146
|
+
if (ids.has(basis.id)) throw new Error(`Workspace hydration receipt mount id must be unique: ${basis.id}`);
|
|
147
|
+
ids.add(basis.id);
|
|
148
|
+
}
|
|
149
|
+
return { version: 2, head, mounts: bases };
|
|
150
|
+
}
|
|
151
|
+
function workspaceHydrationReceiptsEqual(left, right) {
|
|
152
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
153
|
+
}
|
|
154
|
+
function workspaceHydrationReceiptCoversMount(receipt, head, mount) {
|
|
155
|
+
if (!receipt || receipt.head !== head) return false;
|
|
156
|
+
const expected = workspaceHydrationMountBasis(mount);
|
|
157
|
+
const actual = receipt.mounts.find(({ id }) => id === expected.id);
|
|
158
|
+
return Boolean(actual && JSON.stringify(actual) === JSON.stringify(expected));
|
|
159
|
+
}
|
|
160
|
+
function workspaceHydrationReceiptMatchesMounts(receipt, head, mounts) {
|
|
161
|
+
return workspaceHydrationReceiptsEqual(receipt, workspaceHydrationReceipt(head, mounts));
|
|
162
|
+
}
|
|
163
|
+
function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMountIds) {
|
|
164
|
+
const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
|
|
165
|
+
return mounts.filter((mount) => {
|
|
166
|
+
if (deferMountIds?.has(mount.id)) return false;
|
|
167
|
+
if (mount.deleteWhenSourceMissing) return !import_node_fs.default.existsSync(mount.sourcePath);
|
|
168
|
+
const outerPath = import_node_path.default.join(resolvedWorkspacePath, ...normalizedWorkspaceMountPath(mount.workspaceRelativePath).split("/"));
|
|
169
|
+
return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
|
|
173
|
+
const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
|
|
174
|
+
if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
|
|
175
|
+
`) {
|
|
176
|
+
if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
|
|
177
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
178
|
+
}
|
|
179
|
+
return { version: 2, head: legacyHead, mounts: [] };
|
|
180
|
+
}
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(content);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
|
|
186
|
+
}
|
|
187
|
+
if (!parsed || typeof parsed !== "object") {
|
|
188
|
+
throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
|
|
189
|
+
}
|
|
190
|
+
const candidate = parsed;
|
|
191
|
+
if (candidate.version !== 2 || typeof candidate.head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(candidate.head)) {
|
|
192
|
+
throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
|
|
193
|
+
}
|
|
194
|
+
if (!Array.isArray(candidate.mounts)) {
|
|
195
|
+
throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
|
|
196
|
+
}
|
|
197
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
198
|
+
const mounts = candidate.mounts.map((rawMount) => {
|
|
199
|
+
if (!rawMount || typeof rawMount !== "object") {
|
|
200
|
+
throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
|
|
201
|
+
}
|
|
202
|
+
const mount = rawMount;
|
|
203
|
+
const normalizedRelativePath = typeof mount.workspaceRelativePath === "string" ? normalizedWorkspaceMountPath(mount.workspaceRelativePath) : "";
|
|
204
|
+
const durabilityRelative = typeof mount.sourcePath === "string" && typeof mount.durabilityRootPath === "string" ? import_node_path.default.relative(mount.durabilityRootPath, mount.sourcePath) : "..";
|
|
205
|
+
if (typeof mount.id !== "string" || !mount.id || seenIds.has(mount.id) || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative) || typeof mount.workspaceRelativePath !== "string" || mount.workspaceRelativePath !== normalizedRelativePath || !normalizedRelativePath || normalizedRelativePath === "." || normalizedRelativePath.split("/").some((segment) => segment === ".." || segment === ".git") || mount.sourceMode !== "all" && mount.sourceMode !== "git" || mount.hydrateDeletionMode !== "all" && mount.hydrateDeletionMode !== "git" || typeof mount.preserveLocalOnInitialOuterAbsence !== "boolean" || typeof mount.preserveLocalOnHydrationBasisChange !== "boolean" || typeof mount.deleteWhenSourceMissing !== "boolean") {
|
|
206
|
+
throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
|
|
207
|
+
}
|
|
208
|
+
seenIds.add(mount.id);
|
|
209
|
+
return mount;
|
|
210
|
+
});
|
|
211
|
+
const receipt = {
|
|
212
|
+
version: 2,
|
|
213
|
+
head: candidate.head,
|
|
214
|
+
mounts: mounts.sort(compareWorkspaceHydrationMountBasis)
|
|
215
|
+
};
|
|
216
|
+
if (content !== `${JSON.stringify(receipt)}
|
|
217
|
+
`) {
|
|
218
|
+
throw new Error(`Workspace hydration receipt contains a noncanonical mount basis: ${receiptPath}`);
|
|
219
|
+
}
|
|
220
|
+
if (!tryGit(workspacePath, ["cat-file", "-e", `${receipt.head}^{commit}`])) {
|
|
221
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
222
|
+
}
|
|
223
|
+
return receipt;
|
|
224
|
+
}
|
|
225
|
+
function readHydratedWorkspaceReceipt(workspacePath) {
|
|
226
|
+
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
227
|
+
const status = lstatIfExists(receiptPath);
|
|
228
|
+
if (!status) return null;
|
|
229
|
+
if (!status.isFile() || status.isSymbolicLink()) {
|
|
230
|
+
throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
|
|
231
|
+
}
|
|
232
|
+
const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
|
|
233
|
+
return parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content);
|
|
234
|
+
}
|
|
235
|
+
function workspaceGitHydrationIsCurrent(workspacePath, mounts) {
|
|
236
|
+
const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
|
|
237
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(resolvedWorkspacePath, ".git"))) return true;
|
|
238
|
+
if (mounts) validateMounts(resolvedWorkspacePath, mounts);
|
|
239
|
+
const head = revParse(resolvedWorkspacePath, "HEAD");
|
|
240
|
+
if (!workspaceCheckoutDurabilityIsCurrent(resolvedWorkspacePath)) return false;
|
|
241
|
+
if (head === null) return true;
|
|
242
|
+
const receipt = readHydratedWorkspaceReceipt(resolvedWorkspacePath);
|
|
243
|
+
return mounts ? workspaceHydrationReceiptMatchesMounts(receipt, head, configuredWorkspaceHydrationBasisMounts(resolvedWorkspacePath, mounts)) : receipt?.head === head;
|
|
244
|
+
}
|
|
245
|
+
function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
246
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(receipt.head)) {
|
|
247
|
+
throw new Error(`Cannot record invalid fully hydrated workspace object id: ${receipt.head}`);
|
|
248
|
+
}
|
|
249
|
+
if (!tryGit(workspacePath, ["cat-file", "-e", `${receipt.head}^{commit}`])) {
|
|
250
|
+
throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${receipt.head}`);
|
|
251
|
+
}
|
|
252
|
+
readHydratedWorkspaceReceipt(workspacePath);
|
|
253
|
+
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
254
|
+
const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
|
|
255
|
+
if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
|
|
256
|
+
throw new Error(`Workspace Git directory is not a regular directory: ${gitDirectory}`);
|
|
257
|
+
}
|
|
258
|
+
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
259
|
+
const receiptDirectory = import_node_path.default.dirname(receiptPath);
|
|
260
|
+
const existingReceiptDirectory = lstatIfExists(receiptDirectory);
|
|
261
|
+
if (!existingReceiptDirectory) {
|
|
262
|
+
import_node_fs.default.mkdirSync(receiptDirectory, { recursive: false, mode: 448 });
|
|
263
|
+
fsyncDirectory(gitDirectory);
|
|
264
|
+
}
|
|
265
|
+
const receiptDirectoryStatus = import_node_fs.default.lstatSync(receiptDirectory);
|
|
266
|
+
if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
|
|
267
|
+
throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
|
|
268
|
+
}
|
|
269
|
+
const temporaryPath = import_node_path.default.join(receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
270
|
+
let descriptor;
|
|
271
|
+
try {
|
|
272
|
+
descriptor = import_node_fs.default.openSync(temporaryPath, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL, 384);
|
|
273
|
+
import_node_fs.default.writeFileSync(descriptor, `${JSON.stringify(receipt)}
|
|
274
|
+
`, "utf8");
|
|
275
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
276
|
+
import_node_fs.default.closeSync(descriptor);
|
|
277
|
+
descriptor = void 0;
|
|
278
|
+
import_node_fs.default.renameSync(temporaryPath, receiptPath);
|
|
279
|
+
fsyncDirectory(receiptDirectory);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
|
|
282
|
+
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function hydrationTransactionPath(workspacePath) {
|
|
287
|
+
return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
|
|
288
|
+
}
|
|
289
|
+
function workspacePrivateStateDirectory(workspacePath) {
|
|
290
|
+
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
291
|
+
const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
|
|
292
|
+
if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
|
|
293
|
+
throw new Error(`Workspace Git directory is not a regular directory: ${gitDirectory}`);
|
|
294
|
+
}
|
|
295
|
+
const stateDirectory = import_node_path.default.dirname(hydrationTransactionPath(workspacePath));
|
|
296
|
+
const existing = lstatIfExists(stateDirectory);
|
|
297
|
+
if (!existing) {
|
|
298
|
+
import_node_fs.default.mkdirSync(stateDirectory, { recursive: false, mode: 448 });
|
|
299
|
+
fsyncDirectory(gitDirectory);
|
|
300
|
+
} else if (!existing.isDirectory() || existing.isSymbolicLink()) {
|
|
301
|
+
throw new Error(`Workspace Git private state is not a regular directory: ${stateDirectory}`);
|
|
302
|
+
}
|
|
303
|
+
return stateDirectory;
|
|
304
|
+
}
|
|
305
|
+
function writePrivateFileDurably(filePath, content) {
|
|
306
|
+
const descriptor = import_node_fs.default.openSync(
|
|
307
|
+
filePath,
|
|
308
|
+
import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL | import_node_fs.default.constants.O_NOFOLLOW,
|
|
309
|
+
384
|
|
310
|
+
);
|
|
311
|
+
try {
|
|
312
|
+
import_node_fs.default.writeFileSync(descriptor, content, "utf8");
|
|
313
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
314
|
+
} finally {
|
|
315
|
+
import_node_fs.default.closeSync(descriptor);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function fsyncTree(rootPath) {
|
|
319
|
+
const syncEntry = (entryPath) => {
|
|
320
|
+
const status = import_node_fs.default.lstatSync(entryPath);
|
|
321
|
+
if (status.isSymbolicLink()) return;
|
|
322
|
+
if (status.isDirectory()) {
|
|
323
|
+
for (const name of import_node_fs.default.readdirSync(entryPath)) syncEntry(import_node_path.default.join(entryPath, name));
|
|
324
|
+
fsyncDirectory(entryPath);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (!status.isFile()) return;
|
|
328
|
+
const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
329
|
+
try {
|
|
330
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
331
|
+
} finally {
|
|
332
|
+
import_node_fs.default.closeSync(descriptor);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
syncEntry(rootPath);
|
|
336
|
+
}
|
|
337
|
+
function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, content) {
|
|
338
|
+
let parsed;
|
|
339
|
+
try {
|
|
340
|
+
parsed = JSON.parse(content);
|
|
341
|
+
} catch {
|
|
342
|
+
throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
|
|
343
|
+
}
|
|
344
|
+
if (!parsed || typeof parsed !== "object") {
|
|
345
|
+
throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
|
|
346
|
+
}
|
|
347
|
+
const candidate = parsed;
|
|
348
|
+
if (candidate.version !== 1 || candidate.state !== "durable" && candidate.state !== "transition" || candidate.head !== null && (typeof candidate.head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(candidate.head))) {
|
|
349
|
+
throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
|
|
350
|
+
}
|
|
351
|
+
const record = {
|
|
352
|
+
version: 1,
|
|
353
|
+
state: candidate.state,
|
|
354
|
+
head: candidate.head
|
|
355
|
+
};
|
|
356
|
+
if (content !== `${JSON.stringify(record)}
|
|
357
|
+
`) {
|
|
358
|
+
throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
|
|
359
|
+
}
|
|
360
|
+
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
361
|
+
throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
|
|
362
|
+
}
|
|
363
|
+
return record;
|
|
364
|
+
}
|
|
365
|
+
function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
|
|
366
|
+
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
367
|
+
const status = lstatIfExists(recordPath);
|
|
368
|
+
if (!status) return null;
|
|
369
|
+
if (!status.isFile() || status.isSymbolicLink()) {
|
|
370
|
+
throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
|
|
371
|
+
}
|
|
372
|
+
return parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, import_node_fs.default.readFileSync(recordPath, "utf8"));
|
|
373
|
+
}
|
|
374
|
+
function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
375
|
+
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
376
|
+
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
377
|
+
}
|
|
378
|
+
readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
379
|
+
const stateDirectory = workspacePrivateStateDirectory(workspacePath);
|
|
380
|
+
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
381
|
+
const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
382
|
+
try {
|
|
383
|
+
writePrivateFileDurably(temporaryPath, `${JSON.stringify(record)}
|
|
384
|
+
`);
|
|
385
|
+
import_node_fs.default.renameSync(temporaryPath, recordPath);
|
|
386
|
+
fsyncDirectory(stateDirectory);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
393
|
+
const status = import_node_fs.default.lstatSync(workspacePath);
|
|
394
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
395
|
+
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
396
|
+
}
|
|
397
|
+
for (const name of import_node_fs.default.readdirSync(workspacePath)) {
|
|
398
|
+
if (name === ".git") continue;
|
|
399
|
+
fsyncTree(import_node_path.default.join(workspacePath, name));
|
|
400
|
+
}
|
|
401
|
+
fsyncDirectory(workspacePath);
|
|
402
|
+
}
|
|
403
|
+
function workspaceRebaseInProgress(workspacePath) {
|
|
404
|
+
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
405
|
+
return import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-merge")) || import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-apply"));
|
|
406
|
+
}
|
|
407
|
+
function clearWorkspaceCheckoutTree(workspacePath) {
|
|
408
|
+
for (const name of import_node_fs.default.readdirSync(workspacePath)) {
|
|
409
|
+
if (name === ".git") continue;
|
|
410
|
+
import_node_fs.default.rmSync(import_node_path.default.join(workspacePath, name), { recursive: true, force: true });
|
|
411
|
+
}
|
|
412
|
+
fsyncDirectory(workspacePath);
|
|
413
|
+
}
|
|
414
|
+
function recoverWorkspaceCheckoutDurability(workspacePath) {
|
|
415
|
+
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
416
|
+
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
417
|
+
const observedHead = revParse(workspacePath, "HEAD");
|
|
418
|
+
if (record?.state === "durable" && record.head === observedHead) return;
|
|
419
|
+
let targetHead = record?.state === "transition" ? record.head : observedHead;
|
|
420
|
+
if (record?.state === "transition" && observedHead && observedHead !== record.head) {
|
|
421
|
+
const hydrationReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
422
|
+
if (hydrationReceipt?.head === observedHead) {
|
|
423
|
+
targetHead = observedHead;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (workspaceRebaseInProgress(workspacePath)) {
|
|
427
|
+
const abort = gitResult(workspacePath, ["rebase", "--abort"]);
|
|
428
|
+
if (abort.exitCode !== 0) {
|
|
429
|
+
throw new Error(`Abort interrupted workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
430
|
+
}
|
|
431
|
+
if (record?.state !== "transition") targetHead = revParse(workspacePath, "HEAD");
|
|
432
|
+
}
|
|
433
|
+
clearWorkspaceCheckoutTree(workspacePath);
|
|
434
|
+
if (targetHead) {
|
|
435
|
+
git(workspacePath, ["reset", "--hard", targetHead], "reconstruct durable workspace checkout");
|
|
436
|
+
if (revParse(workspacePath, "HEAD") !== targetHead) {
|
|
437
|
+
throw new Error(`Workspace checkout reconstruction did not restore ${targetHead}`);
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
git(workspacePath, ["symbolic-ref", "HEAD", `refs/heads/${WORKSPACE_GIT_BRANCH}`], "restore unborn workspace HEAD");
|
|
441
|
+
git(workspacePath, ["update-ref", "-d", `refs/heads/${WORKSPACE_GIT_BRANCH}`], "remove interrupted workspace branch ref");
|
|
442
|
+
git(workspacePath, ["read-tree", "--empty"], "reconstruct unborn workspace checkout");
|
|
443
|
+
if (revParse(workspacePath, "HEAD") !== null) {
|
|
444
|
+
throw new Error("Workspace checkout reconstruction did not restore its unborn HEAD");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
448
|
+
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "durable", head: targetHead });
|
|
449
|
+
}
|
|
450
|
+
function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
|
|
451
|
+
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
452
|
+
return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
|
|
453
|
+
}
|
|
454
|
+
function beginWorkspaceCheckoutTransition(workspacePath) {
|
|
455
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
456
|
+
const head = revParse(workspacePath, "HEAD");
|
|
457
|
+
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
|
|
458
|
+
}
|
|
459
|
+
function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
460
|
+
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
461
|
+
updateWorkspaceCheckoutDurabilityRecord(workspacePath, {
|
|
462
|
+
version: 1,
|
|
463
|
+
state: "durable",
|
|
464
|
+
head: revParse(workspacePath, "HEAD")
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
468
|
+
try {
|
|
469
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
470
|
+
} catch (recoveryError) {
|
|
471
|
+
throw new AggregateError(
|
|
472
|
+
[error, recoveryError],
|
|
473
|
+
`Workspace checkout mutation failed and its durable head could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
479
|
+
beginWorkspaceCheckoutTransition(workspacePath);
|
|
480
|
+
try {
|
|
481
|
+
mutate();
|
|
482
|
+
completeWorkspaceCheckoutTransition(workspacePath);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function parseHydrationTransactionManifest(transactionPath) {
|
|
488
|
+
const manifestPath = import_node_path.default.join(transactionPath, "manifest.json");
|
|
489
|
+
const workspacePath = import_node_path.default.resolve(transactionPath, "..", "..", "..");
|
|
490
|
+
const status = import_node_fs.default.lstatSync(manifestPath);
|
|
491
|
+
if (!status.isFile() || status.isSymbolicLink()) {
|
|
492
|
+
throw new Error(`Workspace hydration transaction manifest is not a regular file: ${manifestPath}`);
|
|
493
|
+
}
|
|
494
|
+
let parsed;
|
|
495
|
+
try {
|
|
496
|
+
parsed = JSON.parse(import_node_fs.default.readFileSync(manifestPath, "utf8"));
|
|
497
|
+
} catch (error) {
|
|
498
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`, { cause: error });
|
|
499
|
+
}
|
|
500
|
+
if (!parsed || typeof parsed !== "object") {
|
|
501
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
502
|
+
}
|
|
503
|
+
const candidate = parsed;
|
|
504
|
+
if (candidate.version !== 2 || !Array.isArray(candidate.mounts)) {
|
|
505
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
506
|
+
}
|
|
507
|
+
const parseReceipt = (rawReceipt) => {
|
|
508
|
+
if (rawReceipt === null) return null;
|
|
509
|
+
if (!rawReceipt || typeof rawReceipt !== "object") {
|
|
510
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
511
|
+
}
|
|
512
|
+
return parseWorkspaceHydrationReceiptContent(workspacePath, manifestPath, `${JSON.stringify(rawReceipt)}
|
|
513
|
+
`);
|
|
514
|
+
};
|
|
515
|
+
const targetReceipt = parseReceipt(candidate.targetReceipt);
|
|
516
|
+
const receiptBefore = parseReceipt(candidate.receiptBefore);
|
|
517
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
518
|
+
const seenSnapshots = /* @__PURE__ */ new Set();
|
|
519
|
+
const mounts = candidate.mounts.map((rawMount) => {
|
|
520
|
+
if (!rawMount || typeof rawMount !== "object") {
|
|
521
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
522
|
+
}
|
|
523
|
+
const mount = rawMount;
|
|
524
|
+
if (typeof mount.id !== "string" || !mount.id || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || typeof mount.workspaceRelativePath !== "string" || typeof mount.existed !== "boolean" || typeof mount.snapshotName !== "string" || !/^[0-9]+$/.test(mount.snapshotName) || seenIds.has(mount.id) || seenSnapshots.has(mount.snapshotName)) {
|
|
525
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
526
|
+
}
|
|
527
|
+
const relative = import_node_path.default.relative(mount.durabilityRootPath, mount.sourcePath);
|
|
528
|
+
if (!relative || relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
529
|
+
throw new Error(`Workspace hydration transaction source escapes its durability root: ${mount.sourcePath}`);
|
|
530
|
+
}
|
|
531
|
+
seenIds.add(mount.id);
|
|
532
|
+
seenSnapshots.add(mount.snapshotName);
|
|
533
|
+
return mount;
|
|
534
|
+
});
|
|
535
|
+
return {
|
|
536
|
+
version: 2,
|
|
537
|
+
targetReceipt,
|
|
538
|
+
receiptBefore,
|
|
539
|
+
mounts
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function removeHydrationTransaction(workspacePath) {
|
|
543
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
544
|
+
import_node_fs.default.rmSync(transactionPath, { recursive: true, force: true });
|
|
545
|
+
fsyncDirectory(import_node_path.default.dirname(transactionPath));
|
|
546
|
+
}
|
|
547
|
+
function hydrationTransactionCommitted(transactionPath) {
|
|
548
|
+
const markerPath = import_node_path.default.join(transactionPath, "committed");
|
|
549
|
+
const markerStatus = lstatIfExists(markerPath);
|
|
550
|
+
if (!markerStatus) return false;
|
|
551
|
+
if (!markerStatus.isFile() || markerStatus.isSymbolicLink() || import_node_fs.default.readFileSync(markerPath, "utf8") !== "committed\n") {
|
|
552
|
+
throw new Error(`Workspace hydration transaction commit marker is invalid: ${markerPath}`);
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
function restoreHydrationTransaction(workspacePath, manifest) {
|
|
557
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
558
|
+
for (const mount of manifest.mounts) {
|
|
559
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
560
|
+
trustedRoot: mount.durabilityRootPath,
|
|
561
|
+
candidate: mount.sourcePath,
|
|
562
|
+
label: `Workspace hydration recovery mount ${mount.id}`
|
|
563
|
+
});
|
|
564
|
+
if (!mount.existed) {
|
|
565
|
+
import_node_fs.default.rmSync(mount.sourcePath, { recursive: true, force: true });
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const snapshotPath = import_node_path.default.join(transactionPath, "mounts", mount.snapshotName);
|
|
569
|
+
const snapshotStatus = import_node_fs.default.lstatSync(snapshotPath);
|
|
570
|
+
if (!snapshotStatus.isDirectory() || snapshotStatus.isSymbolicLink()) {
|
|
571
|
+
throw new Error(`Workspace hydration snapshot is not a regular directory: ${snapshotPath}`);
|
|
572
|
+
}
|
|
573
|
+
(0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: snapshotPath, targetRoot: mount.sourcePath, sourceMode: "all", deletionMode: "all" });
|
|
574
|
+
}
|
|
575
|
+
fsyncHydratedWorkspaceMounts(
|
|
576
|
+
manifest.mounts.map((mount) => ({
|
|
577
|
+
...mount,
|
|
578
|
+
hydrationIncarnationKey: mount.incarnationKey,
|
|
579
|
+
sourceMode: "all",
|
|
580
|
+
hydrateDeletionMode: "all"
|
|
581
|
+
}))
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
function recoverPendingHydrationTransaction(workspacePath) {
|
|
585
|
+
const stateDirectory = import_node_path.default.dirname(hydrationTransactionPath(workspacePath));
|
|
586
|
+
const stateStatus = lstatIfExists(stateDirectory);
|
|
587
|
+
if (!stateStatus) return;
|
|
588
|
+
if (!stateStatus.isDirectory() || stateStatus.isSymbolicLink()) {
|
|
589
|
+
throw new Error(`Workspace Git private state is not a regular directory: ${stateDirectory}`);
|
|
590
|
+
}
|
|
591
|
+
for (const name of import_node_fs.default.readdirSync(stateDirectory)) {
|
|
592
|
+
if (!name.startsWith(".workspace-hydration-transaction.") || !name.endsWith(".tmp")) continue;
|
|
593
|
+
import_node_fs.default.rmSync(import_node_path.default.join(stateDirectory, name), { recursive: true, force: true });
|
|
594
|
+
fsyncDirectory(stateDirectory);
|
|
595
|
+
}
|
|
596
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
597
|
+
const transactionStatus = lstatIfExists(transactionPath);
|
|
598
|
+
if (!transactionStatus) return;
|
|
599
|
+
if (!transactionStatus.isDirectory() || transactionStatus.isSymbolicLink()) {
|
|
600
|
+
throw new Error(`Workspace hydration transaction is not a regular directory: ${transactionPath}`);
|
|
601
|
+
}
|
|
602
|
+
const manifest = parseHydrationTransactionManifest(transactionPath);
|
|
603
|
+
if (hydrationTransactionCommitted(transactionPath)) {
|
|
604
|
+
const receipt2 = readHydratedWorkspaceReceipt(workspacePath);
|
|
605
|
+
if (manifest.targetReceipt && !workspaceHydrationReceiptsEqual(receipt2, manifest.targetReceipt)) {
|
|
606
|
+
updateHydratedWorkspaceReceipt(workspacePath, manifest.targetReceipt);
|
|
607
|
+
}
|
|
608
|
+
removeHydrationTransaction(workspacePath);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const receipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
612
|
+
if (manifest.targetReceipt && !workspaceHydrationReceiptsEqual(manifest.targetReceipt, manifest.receiptBefore) && workspaceHydrationReceiptsEqual(receipt, manifest.targetReceipt)) {
|
|
613
|
+
removeHydrationTransaction(workspacePath);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
617
|
+
removeHydrationTransaction(workspacePath);
|
|
618
|
+
}
|
|
619
|
+
function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
620
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
621
|
+
const stateDirectory = workspacePrivateStateDirectory(workspacePath);
|
|
622
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
623
|
+
const stagingPath = import_node_path.default.join(stateDirectory, `.workspace-hydration-transaction.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
624
|
+
import_node_fs.default.mkdirSync(stagingPath, { mode: 448 });
|
|
625
|
+
try {
|
|
626
|
+
const snapshotRoot = import_node_path.default.join(stagingPath, "mounts");
|
|
627
|
+
import_node_fs.default.mkdirSync(snapshotRoot, { mode: 448 });
|
|
628
|
+
const manifest = {
|
|
629
|
+
version: 2,
|
|
630
|
+
targetReceipt,
|
|
631
|
+
receiptBefore: readHydratedWorkspaceReceipt(workspacePath),
|
|
632
|
+
mounts: mounts.map((mount, index) => {
|
|
633
|
+
const sourcePath = import_node_path.default.resolve(mount.sourcePath);
|
|
634
|
+
const durabilityRootPath = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
635
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
636
|
+
trustedRoot: durabilityRootPath,
|
|
637
|
+
candidate: sourcePath,
|
|
638
|
+
label: `Workspace hydration snapshot mount ${mount.id}`
|
|
639
|
+
});
|
|
640
|
+
const sourceStatus = lstatIfExists(sourcePath);
|
|
641
|
+
if (sourceStatus && (!sourceStatus.isDirectory() || sourceStatus.isSymbolicLink())) {
|
|
642
|
+
throw new Error(`Workspace hydration snapshot source is not a regular directory: ${sourcePath}`);
|
|
643
|
+
}
|
|
644
|
+
const snapshotName = String(index);
|
|
645
|
+
const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
|
|
646
|
+
import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
|
|
647
|
+
if (sourceStatus) {
|
|
648
|
+
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
649
|
+
sourceRoot: sourcePath,
|
|
650
|
+
targetRoot: snapshotPath,
|
|
651
|
+
sourceMode: "all",
|
|
652
|
+
deletionMode: "all",
|
|
653
|
+
// This tree is still private and the source authority is untouched.
|
|
654
|
+
// The single recursive barrier below makes the complete snapshot
|
|
655
|
+
// durable before its directory entry is published or hydration starts.
|
|
656
|
+
durability: "deferred_private_staging"
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
id: mount.id,
|
|
661
|
+
incarnationKey: mount.hydrationIncarnationKey,
|
|
662
|
+
sourcePath,
|
|
663
|
+
durabilityRootPath,
|
|
664
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
665
|
+
existed: Boolean(sourceStatus),
|
|
666
|
+
snapshotName
|
|
667
|
+
};
|
|
668
|
+
})
|
|
669
|
+
};
|
|
670
|
+
writePrivateFileDurably(import_node_path.default.join(stagingPath, "manifest.json"), `${JSON.stringify(manifest)}
|
|
671
|
+
`);
|
|
672
|
+
fsyncTree(stagingPath);
|
|
673
|
+
import_node_fs.default.renameSync(stagingPath, transactionPath);
|
|
674
|
+
fsyncDirectory(stateDirectory);
|
|
675
|
+
return manifest;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
import_node_fs.default.rmSync(stagingPath, { recursive: true, force: true });
|
|
678
|
+
fsyncDirectory(stateDirectory);
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function markHydrationTransactionDurable(workspacePath) {
|
|
683
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
684
|
+
writePrivateFileDurably(import_node_path.default.join(transactionPath, "committed"), "committed\n");
|
|
685
|
+
fsyncDirectory(transactionPath);
|
|
686
|
+
}
|
|
687
|
+
function fsyncHydratedWorkspaceMount(mount) {
|
|
688
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
689
|
+
trustedRoot: mount.durabilityRootPath,
|
|
690
|
+
candidate: mount.sourcePath,
|
|
691
|
+
label: `Workspace mount ${mount.id} source`
|
|
692
|
+
});
|
|
693
|
+
const syncEntry = (entryPath, root) => {
|
|
694
|
+
const status = import_node_fs.default.lstatSync(entryPath);
|
|
695
|
+
if (status.isSymbolicLink()) return;
|
|
696
|
+
if (status.isDirectory()) {
|
|
697
|
+
for (const name of import_node_fs.default.readdirSync(entryPath)) {
|
|
698
|
+
if (root && name === ".git") continue;
|
|
699
|
+
syncEntry(import_node_path.default.join(entryPath, name), false);
|
|
700
|
+
}
|
|
701
|
+
fsyncDirectory(entryPath);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (!status.isFile()) return;
|
|
705
|
+
const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY);
|
|
706
|
+
try {
|
|
707
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
708
|
+
} finally {
|
|
709
|
+
import_node_fs.default.closeSync(descriptor);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
if (import_node_fs.default.existsSync(mount.sourcePath)) syncEntry(mount.sourcePath, true);
|
|
713
|
+
const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
714
|
+
let boundaryParent = import_node_path.default.dirname(import_node_path.default.resolve(mount.sourcePath));
|
|
715
|
+
while (true) {
|
|
716
|
+
if (import_node_fs.default.existsSync(boundaryParent)) {
|
|
717
|
+
const boundaryStatus = import_node_fs.default.lstatSync(boundaryParent);
|
|
718
|
+
if (!boundaryStatus.isDirectory() || boundaryStatus.isSymbolicLink()) {
|
|
719
|
+
throw new Error(`Workspace mount durability boundary is not a regular directory: ${boundaryParent}`);
|
|
720
|
+
}
|
|
721
|
+
fsyncDirectory(boundaryParent);
|
|
722
|
+
}
|
|
723
|
+
if (boundaryParent === durabilityRoot) break;
|
|
724
|
+
const nextParent = import_node_path.default.dirname(boundaryParent);
|
|
725
|
+
if (nextParent === boundaryParent) {
|
|
726
|
+
throw new Error(`Workspace mount ${mount.sourcePath} does not descend from durability root ${durabilityRoot}`);
|
|
727
|
+
}
|
|
728
|
+
boundaryParent = nextParent;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function fsyncHydratedWorkspaceMounts(mounts) {
|
|
732
|
+
for (const mount of mounts) fsyncHydratedWorkspaceMount(mount);
|
|
733
|
+
}
|
|
91
734
|
function workspaceIsShallow(workspacePath) {
|
|
92
735
|
return gitResult(workspacePath, ["rev-parse", "--is-shallow-repository"]).stdout === "true";
|
|
93
736
|
}
|
|
@@ -100,7 +743,22 @@ function validateMounts(workspacePath, mounts) {
|
|
|
100
743
|
const paths = [];
|
|
101
744
|
for (const mount of mounts) {
|
|
102
745
|
if (!mount.id || ids.has(mount.id)) throw new Error(`Workspace mount id must be unique: ${mount.id}`);
|
|
746
|
+
if (!mount.hydrationIncarnationKey || mount.hydrationIncarnationKey.includes("\0")) {
|
|
747
|
+
throw new Error(`Workspace mount hydration incarnation is invalid: ${mount.id}`);
|
|
748
|
+
}
|
|
103
749
|
ids.add(mount.id);
|
|
750
|
+
const sourcePath = import_node_path.default.resolve(mount.sourcePath);
|
|
751
|
+
const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
752
|
+
const durabilityRelative = import_node_path.default.relative(durabilityRoot, sourcePath);
|
|
753
|
+
const durabilityRootStatus = import_node_fs.default.lstatSync(durabilityRoot);
|
|
754
|
+
if (!durabilityRootStatus.isDirectory() || durabilityRootStatus.isSymbolicLink() || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative)) {
|
|
755
|
+
throw new Error(`Workspace mount source escapes its durability root: ${mount.sourcePath}`);
|
|
756
|
+
}
|
|
757
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
758
|
+
trustedRoot: durabilityRoot,
|
|
759
|
+
candidate: sourcePath,
|
|
760
|
+
label: `Workspace mount ${mount.id} source`
|
|
761
|
+
});
|
|
104
762
|
const normalized = mount.workspaceRelativePath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
105
763
|
if (!normalized || normalized === "." || normalized.split("/").some((segment) => segment === ".." || segment === ".git")) {
|
|
106
764
|
throw new Error(`Invalid workspace mount path: ${mount.workspaceRelativePath}`);
|
|
@@ -147,6 +805,8 @@ function configureWorkspaceRepository(input) {
|
|
|
147
805
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
148
806
|
git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
|
|
149
807
|
git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
|
|
808
|
+
git(input.workspacePath, ["config", "--local", "core.fsync", "committed"], "configure durable workspace commits");
|
|
809
|
+
git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
150
810
|
}
|
|
151
811
|
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
152
812
|
const result = gitResult(workspacePath, [
|
|
@@ -170,7 +830,24 @@ ${result.stdout}`;
|
|
|
170
830
|
}
|
|
171
831
|
function ensureWorkspaceGitClone(input) {
|
|
172
832
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
833
|
+
const workspaceParent = import_node_path.default.dirname(workspacePath);
|
|
834
|
+
const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
|
|
835
|
+
if (!parentStatus.isDirectory() || parentStatus.isSymbolicLink()) {
|
|
836
|
+
throw new Error(`Workspace clone parent is not a regular directory: ${workspaceParent}`);
|
|
837
|
+
}
|
|
838
|
+
const workspaceStatus = lstatIfExists(workspacePath);
|
|
839
|
+
if (workspaceStatus) {
|
|
840
|
+
if (!workspaceStatus.isDirectory() || workspaceStatus.isSymbolicLink()) {
|
|
841
|
+
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
173
844
|
const gitPath = import_node_path.default.join(workspacePath, ".git");
|
|
845
|
+
const gitStatus = lstatIfExists(gitPath);
|
|
846
|
+
if (gitStatus) {
|
|
847
|
+
if (!gitStatus.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
848
|
+
throw new Error(`Workspace clone Git metadata is not a regular directory: ${gitPath}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
174
851
|
if (!import_node_fs.default.existsSync(gitPath)) {
|
|
175
852
|
import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
|
|
176
853
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(workspacePath), { recursive: true });
|
|
@@ -192,6 +869,7 @@ function ensureWorkspaceGitClone(input) {
|
|
|
192
869
|
}
|
|
193
870
|
}
|
|
194
871
|
configureWorkspaceRepository({ ...input, workspacePath });
|
|
872
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
195
873
|
const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
196
874
|
const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
|
|
197
875
|
if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
@@ -200,17 +878,21 @@ function ensureWorkspaceGitClone(input) {
|
|
|
200
878
|
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
201
879
|
let localHead = revParse(workspacePath, "HEAD");
|
|
202
880
|
if (!localHead && remoteHead) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
881
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
882
|
+
git(
|
|
883
|
+
workspacePath,
|
|
884
|
+
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
885
|
+
"check out workspace main"
|
|
886
|
+
);
|
|
887
|
+
});
|
|
208
888
|
localHead = remoteHead;
|
|
209
889
|
}
|
|
210
890
|
if (localHead && remoteHead && localHead === remoteHead) {
|
|
211
891
|
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
212
892
|
} else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
|
|
213
|
-
|
|
893
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
894
|
+
git(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
|
|
895
|
+
});
|
|
214
896
|
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
215
897
|
localHead = remoteHead;
|
|
216
898
|
}
|
|
@@ -230,6 +912,16 @@ function activeMounts(mounts) {
|
|
|
230
912
|
}
|
|
231
913
|
function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
232
914
|
for (const mount of mounts) {
|
|
915
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
916
|
+
trustedRoot: mount.durabilityRootPath,
|
|
917
|
+
candidate: mount.sourcePath,
|
|
918
|
+
label: `Workspace mount ${mount.id} source`
|
|
919
|
+
});
|
|
920
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
921
|
+
trustedRoot: workspacePath,
|
|
922
|
+
candidate: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
923
|
+
label: `Workspace mount ${mount.id} outer target`
|
|
924
|
+
});
|
|
233
925
|
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
234
926
|
sourceRoot: mount.sourcePath,
|
|
235
927
|
targetRoot: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
@@ -241,6 +933,11 @@ function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
|
241
933
|
function removeWorkspaceMounts(workspacePath, mounts) {
|
|
242
934
|
for (const mount of mounts) {
|
|
243
935
|
const targetPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
936
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
937
|
+
trustedRoot: workspacePath,
|
|
938
|
+
candidate: targetPath,
|
|
939
|
+
label: `Workspace mount ${mount.id} outer target`
|
|
940
|
+
});
|
|
244
941
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(workspacePath), import_node_path.default.resolve(targetPath));
|
|
245
942
|
if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
246
943
|
throw new Error(`Workspace mount escapes the workspace clone: ${mount.workspaceRelativePath}`);
|
|
@@ -248,9 +945,45 @@ function removeWorkspaceMounts(workspacePath, mounts) {
|
|
|
248
945
|
import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
|
|
249
946
|
}
|
|
250
947
|
}
|
|
251
|
-
function
|
|
948
|
+
function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
|
|
949
|
+
const head = revParse(workspacePath, "HEAD");
|
|
252
950
|
for (const mount of mounts) {
|
|
951
|
+
const normalized = normalizedWorkspaceMountPath(mount.workspaceRelativePath);
|
|
952
|
+
const targetPath = import_node_path.default.join(workspacePath, ...normalized.split("/"));
|
|
953
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
954
|
+
trustedRoot: workspacePath,
|
|
955
|
+
candidate: targetPath,
|
|
956
|
+
label: `Deferred workspace mount ${mount.id} outer target`
|
|
957
|
+
});
|
|
958
|
+
import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
|
|
959
|
+
if (head && tryGit(workspacePath, ["cat-file", "-e", `${head}:${normalized}`])) {
|
|
960
|
+
git(
|
|
961
|
+
workspacePath,
|
|
962
|
+
["checkout", "--no-recurse-submodules", head, "--", `:(literal)${normalized}`],
|
|
963
|
+
`restore deferred workspace mount ${mount.id} from its integration basis`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
969
|
+
const hydratedMountIds = [];
|
|
970
|
+
const skippedMountIds = [];
|
|
971
|
+
for (const mount of mounts) {
|
|
972
|
+
if (!options.ignoreBusy && mount.busy?.()) {
|
|
973
|
+
skippedMountIds.push(mount.id);
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
253
976
|
const sourceRoot = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
977
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
978
|
+
trustedRoot: workspacePath,
|
|
979
|
+
candidate: sourceRoot,
|
|
980
|
+
label: `Workspace mount ${mount.id} outer source`
|
|
981
|
+
});
|
|
982
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
983
|
+
trustedRoot: mount.durabilityRootPath,
|
|
984
|
+
candidate: mount.sourcePath,
|
|
985
|
+
label: `Workspace mount ${mount.id} hydration target`
|
|
986
|
+
});
|
|
254
987
|
if (!import_node_fs.default.existsSync(sourceRoot)) {
|
|
255
988
|
if (mount.hydrateDeletionMode === "all") {
|
|
256
989
|
import_node_fs.default.rmSync(mount.sourcePath, { recursive: true, force: true });
|
|
@@ -267,37 +1000,167 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts) {
|
|
|
267
1000
|
import_node_fs.default.rmSync(emptyRoot, { recursive: true, force: true });
|
|
268
1001
|
}
|
|
269
1002
|
}
|
|
1003
|
+
hydratedMountIds.push(mount.id);
|
|
270
1004
|
continue;
|
|
271
1005
|
}
|
|
1006
|
+
const targetExists = import_node_fs.default.existsSync(mount.sourcePath);
|
|
272
1007
|
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
273
1008
|
sourceRoot,
|
|
274
1009
|
targetRoot: mount.sourcePath,
|
|
275
1010
|
sourceMode: "all",
|
|
276
|
-
|
|
1011
|
+
// A missing checkout has no Git index to define git-only deletions. Its
|
|
1012
|
+
// newly created directory is empty, so all-mode is equivalent and lets
|
|
1013
|
+
// bootstrap hydration preserve the fetched tree before Git initializes.
|
|
1014
|
+
deletionMode: targetExists ? mount.hydrateDeletionMode : "all"
|
|
277
1015
|
});
|
|
1016
|
+
hydratedMountIds.push(mount.id);
|
|
1017
|
+
}
|
|
1018
|
+
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
|
|
1019
|
+
}
|
|
1020
|
+
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1021
|
+
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1022
|
+
const mountIds = input.mounts.map(({ id }) => id).sort();
|
|
1023
|
+
const requiredIds = (input.requiredMounts ?? input.mounts).map(({ id }) => id).sort();
|
|
1024
|
+
if (mountIds.length !== requiredIds.length || mountIds.some((id, index) => id !== requiredIds[index])) {
|
|
1025
|
+
return { hydratedMountIds: [], skippedMountIds: requiredIds };
|
|
1026
|
+
}
|
|
1027
|
+
const busyMountIds = input.ignoreBusy ? [] : (input.durabilityMounts ?? input.mounts).filter((mount) => mount.busy?.()).map(({ id }) => id).sort();
|
|
1028
|
+
if (busyMountIds.length > 0) {
|
|
1029
|
+
return { hydratedMountIds: [], skippedMountIds: busyMountIds };
|
|
1030
|
+
}
|
|
1031
|
+
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1032
|
+
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1033
|
+
}
|
|
1034
|
+
const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
|
|
1035
|
+
const targetReceipt = targetHead ? workspaceHydrationReceipt(targetHead, input.receiptMounts ?? input.durabilityMounts ?? input.mounts) : null;
|
|
1036
|
+
const manifest = beginHydrationTransaction(workspacePath, input.mounts, targetReceipt);
|
|
1037
|
+
try {
|
|
1038
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, input.mounts, { ignoreBusy: true });
|
|
1039
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== requiredIds.length || hydration.hydratedMountIds.some((id, index) => id !== requiredIds[index])) {
|
|
1040
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1041
|
+
}
|
|
1042
|
+
fsyncHydratedWorkspaceMounts(input.durabilityMounts ?? input.mounts);
|
|
1043
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1044
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1045
|
+
removeHydrationTransaction(workspacePath);
|
|
1046
|
+
return hydration;
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1049
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1050
|
+
throw error;
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1054
|
+
removeHydrationTransaction(workspacePath);
|
|
1055
|
+
} catch (restoreError) {
|
|
1056
|
+
throw new AggregateError(
|
|
1057
|
+
[error, restoreError],
|
|
1058
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1065
|
+
const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
|
|
1066
|
+
if (!import_node_fs.default.existsSync(gitDirectory)) {
|
|
1067
|
+
return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
|
|
1068
|
+
}
|
|
1069
|
+
recoverWorkspaceCheckoutDurability(import_node_path.default.resolve(workspacePath));
|
|
1070
|
+
return hydrateWorkspaceGitMountsTransactionally({
|
|
1071
|
+
workspacePath,
|
|
1072
|
+
mounts,
|
|
1073
|
+
ignoreBusy: options.ignoreBusy
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1077
|
+
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
1078
|
+
validateMounts(workspacePath, mounts);
|
|
1079
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
1080
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
1081
|
+
const currentHead = revParse(workspacePath, "HEAD");
|
|
1082
|
+
if (!currentHead) return;
|
|
1083
|
+
const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1084
|
+
const outerHeadContainsMount = (mount) => tryGit(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1085
|
+
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1086
|
+
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1087
|
+
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
1088
|
+
);
|
|
1089
|
+
if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
|
|
1090
|
+
const busyMountIds = options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1091
|
+
if (busyMountIds.length > 0) {
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${busyMountIds.join(", ")}`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const hydrationTargets = uncoveredBasisMounts.filter(
|
|
1097
|
+
(mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
|
|
1098
|
+
);
|
|
1099
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1100
|
+
workspacePath,
|
|
1101
|
+
mounts: hydrationTargets,
|
|
1102
|
+
durabilityMounts: uncoveredBasisMounts,
|
|
1103
|
+
receiptMounts: configuredBasisMounts,
|
|
1104
|
+
requiredMounts: hydrationTargets,
|
|
1105
|
+
ignoreBusy: options.ignoreBusy,
|
|
1106
|
+
recordCurrentHead: true
|
|
1107
|
+
});
|
|
1108
|
+
if (hydration.skippedMountIds.length > 0) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${hydration.skippedMountIds.join(", ")}`
|
|
1111
|
+
);
|
|
278
1112
|
}
|
|
279
1113
|
}
|
|
280
1114
|
function resetWorkspaceGit(input) {
|
|
281
1115
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
282
1116
|
validateMounts(workspacePath, input.mounts);
|
|
283
1117
|
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1118
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
284
1119
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
285
1120
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
286
1121
|
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
287
1122
|
if (remoteHead) {
|
|
288
|
-
|
|
289
|
-
|
|
1123
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1124
|
+
git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
|
|
1125
|
+
git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
|
|
1126
|
+
});
|
|
290
1127
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
291
|
-
|
|
292
|
-
|
|
1128
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1129
|
+
git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
|
|
1130
|
+
git(workspacePath, ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })], "record empty workspace reset");
|
|
1131
|
+
});
|
|
293
1132
|
}
|
|
294
1133
|
const selected = activeMounts(input.mounts);
|
|
295
|
-
|
|
1134
|
+
const requiredLiveMountsBeforeHydration = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
1135
|
+
const bootstrapMounts = selected.skipped.filter((mount) => {
|
|
1136
|
+
if (mount.deleteWhenSourceMissing || import_node_fs.default.existsSync(mount.sourcePath) || mount.busy?.()) return false;
|
|
1137
|
+
const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1138
|
+
return import_node_fs.default.existsSync(outerPath);
|
|
1139
|
+
});
|
|
1140
|
+
const hydrationMounts = [...selected.active, ...bootstrapMounts];
|
|
1141
|
+
const requiredLiveMounts = [
|
|
1142
|
+
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
1143
|
+
];
|
|
1144
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1145
|
+
workspacePath,
|
|
1146
|
+
mounts: hydrationMounts,
|
|
1147
|
+
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
|
|
1148
|
+
receiptMounts: input.mounts,
|
|
1149
|
+
requiredMounts: requiredLiveMounts,
|
|
1150
|
+
recordCurrentHead: true
|
|
1151
|
+
});
|
|
1152
|
+
const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
|
|
1153
|
+
const resetHead = revParse(workspacePath, "HEAD");
|
|
1154
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== requiredLiveIds.length || hydration.hydratedMountIds.some((id, index) => id !== requiredLiveIds[index]) || resetHead && !workspaceHydrationReceiptMatchesMounts(readHydratedWorkspaceReceipt(workspacePath), resetHead, input.mounts)) {
|
|
1155
|
+
throw new Error("Workspace reset could not durably hydrate every live mount; remediation remains active");
|
|
1156
|
+
}
|
|
296
1157
|
return {
|
|
297
1158
|
startingHead: initial.localHead,
|
|
298
1159
|
localHead: revParse(workspacePath, "HEAD"),
|
|
299
1160
|
remoteHead,
|
|
300
|
-
discardedPaths
|
|
1161
|
+
discardedPaths,
|
|
1162
|
+
activeMountIds: hydration.hydratedMountIds,
|
|
1163
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
301
1164
|
};
|
|
302
1165
|
}
|
|
303
1166
|
function stagedPaths(workspacePath) {
|
|
@@ -362,17 +1225,22 @@ function snapshotConflict(input) {
|
|
|
362
1225
|
}
|
|
363
1226
|
function rebaseWorkspace(input) {
|
|
364
1227
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
1228
|
+
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
365
1229
|
const rebase = gitResult(
|
|
366
1230
|
input.workspacePath,
|
|
367
1231
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
368
1232
|
);
|
|
369
|
-
if (rebase.exitCode === 0)
|
|
1233
|
+
if (rebase.exitCode === 0) {
|
|
1234
|
+
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1235
|
+
return { ok: true };
|
|
1236
|
+
}
|
|
370
1237
|
const conflicts = conflictPaths(input.workspacePath);
|
|
371
1238
|
const refs = snapshotConflict(input);
|
|
372
1239
|
const abort = gitResult(input.workspacePath, ["rebase", "--abort"]);
|
|
373
1240
|
if (abort.exitCode !== 0) {
|
|
374
1241
|
throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
375
1242
|
}
|
|
1243
|
+
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
376
1244
|
return {
|
|
377
1245
|
ok: false,
|
|
378
1246
|
conflictPaths: conflicts,
|
|
@@ -382,26 +1250,31 @@ function rebaseWorkspace(input) {
|
|
|
382
1250
|
}
|
|
383
1251
|
function synchronizeWithFetchedHead(input) {
|
|
384
1252
|
const localHead = revParse(input.workspacePath, "HEAD");
|
|
385
|
-
|
|
1253
|
+
const remoteHead = input.remoteHead;
|
|
1254
|
+
if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
|
|
386
1255
|
if (!localHead) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1256
|
+
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1257
|
+
git(
|
|
1258
|
+
input.workspacePath,
|
|
1259
|
+
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1260
|
+
"check out fetched workspace main"
|
|
1261
|
+
);
|
|
1262
|
+
});
|
|
392
1263
|
return { kind: "ready", updated: true, rebased: false };
|
|
393
1264
|
}
|
|
394
|
-
if (localHead ===
|
|
395
|
-
updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1265
|
+
if (localHead === remoteHead) {
|
|
1266
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
396
1267
|
return { kind: "ready", updated: false, rebased: false };
|
|
397
1268
|
}
|
|
398
|
-
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead,
|
|
399
|
-
|
|
400
|
-
|
|
1269
|
+
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
|
|
1270
|
+
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1271
|
+
git(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
|
|
1272
|
+
});
|
|
1273
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
401
1274
|
return { kind: "ready", updated: true, rebased: false };
|
|
402
1275
|
}
|
|
403
|
-
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor",
|
|
404
|
-
updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1276
|
+
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
|
|
1277
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
405
1278
|
return { kind: "ready", updated: false, rebased: false };
|
|
406
1279
|
}
|
|
407
1280
|
const integratedHead = revParse(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
|
|
@@ -414,11 +1287,11 @@ function synchronizeWithFetchedHead(input) {
|
|
|
414
1287
|
const rebased = rebaseWorkspace({
|
|
415
1288
|
workspacePath: input.workspacePath,
|
|
416
1289
|
localHead,
|
|
417
|
-
remoteHead
|
|
1290
|
+
remoteHead,
|
|
418
1291
|
attemptId: input.attemptId,
|
|
419
1292
|
truncatedHistoryBase
|
|
420
1293
|
});
|
|
421
|
-
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1294
|
+
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
422
1295
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
423
1296
|
}
|
|
424
1297
|
async function synchronizeWorkspaceGit(input) {
|
|
@@ -427,25 +1300,131 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
427
1300
|
const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
|
|
428
1301
|
const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
|
|
429
1302
|
validateMounts(workspacePath, input.mounts);
|
|
1303
|
+
const busyLiveMountIds = input.skipMountMirror ? [] : input.mounts.filter((mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()).map(({ id }) => id).sort();
|
|
1304
|
+
if (busyLiveMountIds.length > 0) {
|
|
1305
|
+
const cloneExists = import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git"));
|
|
1306
|
+
const localHead = cloneExists ? revParse(workspacePath, "HEAD") : null;
|
|
1307
|
+
const remoteHead2 = cloneExists ? revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`) : null;
|
|
1308
|
+
return {
|
|
1309
|
+
outcome: "no_change",
|
|
1310
|
+
startingHead: localHead,
|
|
1311
|
+
localHead,
|
|
1312
|
+
remoteHead: remoteHead2,
|
|
1313
|
+
publishedHead: remoteHead2,
|
|
1314
|
+
rebaseCount: 0,
|
|
1315
|
+
diffSizeBytes: 0,
|
|
1316
|
+
affectedPaths: [],
|
|
1317
|
+
activeMountIds: [],
|
|
1318
|
+
skippedMountIds: input.mounts.map(({ id }) => id).sort()
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
430
1321
|
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
431
1322
|
const startingHead = initial.localHead;
|
|
1323
|
+
const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1324
|
+
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1325
|
+
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead && receiptBeforeInitialHydration?.head === initial.localHead) {
|
|
1326
|
+
for (const mount of input.mounts) {
|
|
1327
|
+
if (!mount.deleteWhenSourceMissing && mount.preserveLocalOnInitialOuterAbsence !== true && mount.preserveLocalOnHydrationBasisChange !== true && !workspaceHydrationReceiptCoversMount(receiptBeforeInitialHydration, initial.localHead, mount)) {
|
|
1328
|
+
deferredMountIds.add(mount.id);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1333
|
+
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {}
|
|
1334
|
+
});
|
|
1335
|
+
const receiptRequiredLiveMounts = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
432
1336
|
const selected = activeMounts(input.mounts);
|
|
1337
|
+
const projectedActiveMounts = selected.active.filter(({ id }) => !deferredMountIds.has(id));
|
|
1338
|
+
const projectedTombstones = selected.tombstones.filter(({ id }) => !deferredMountIds.has(id));
|
|
1339
|
+
const projectionBasisHead = revParse(workspacePath, "HEAD");
|
|
1340
|
+
const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1341
|
+
const projectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts).filter(
|
|
1342
|
+
(mount) => !deferredMountIds.has(mount.id) && Boolean(projectionBasisHead && workspaceHydrationReceiptCoversMount(projectionBasisReceipt, projectionBasisHead, mount))
|
|
1343
|
+
);
|
|
1344
|
+
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1345
|
+
const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
|
|
1346
|
+
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1347
|
+
if (currentHeadBeforeHydration && workspaceHydrationReceiptMatchesMounts(
|
|
1348
|
+
readHydratedWorkspaceReceipt(workspacePath),
|
|
1349
|
+
currentHeadBeforeHydration,
|
|
1350
|
+
completionReceiptMounts
|
|
1351
|
+
)) {
|
|
1352
|
+
const lateBusyIds = selected.active.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1353
|
+
const lateBusy = new Set(lateBusyIds);
|
|
1354
|
+
return {
|
|
1355
|
+
activeMountIds: [...selected.active.filter(({ id }) => !lateBusy.has(id)), ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1356
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...lateBusyIds])].sort()
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1360
|
+
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1361
|
+
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
1362
|
+
);
|
|
1363
|
+
const uncoveredCompletionMounts = selected.active.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1364
|
+
const uncoveredRequiredLiveMounts = receiptRequiredLiveMounts.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1365
|
+
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1366
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1367
|
+
workspacePath,
|
|
1368
|
+
mounts: uncoveredCompletionMounts,
|
|
1369
|
+
durabilityMounts: uncoveredCompletionBasisMounts,
|
|
1370
|
+
receiptMounts: completionReceiptMounts,
|
|
1371
|
+
requiredMounts: uncoveredRequiredLiveMounts,
|
|
1372
|
+
recordCurrentHead: true
|
|
1373
|
+
});
|
|
1374
|
+
if (requireCurrentHeadReceipt) {
|
|
1375
|
+
const currentHead = revParse(workspacePath, "HEAD");
|
|
1376
|
+
if (currentHead && !workspaceHydrationReceiptMatchesMounts(readHydratedWorkspaceReceipt(workspacePath), currentHead, completionReceiptMounts)) {
|
|
1377
|
+
throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1382
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1383
|
+
};
|
|
1384
|
+
};
|
|
1385
|
+
let newlyStagedPaths = [];
|
|
1386
|
+
const stageAndCommitWorkspace = () => {
|
|
1387
|
+
git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1388
|
+
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1389
|
+
if (newlyStagedPaths.length > 0) {
|
|
1390
|
+
git(
|
|
1391
|
+
workspacePath,
|
|
1392
|
+
[
|
|
1393
|
+
"commit",
|
|
1394
|
+
"-m",
|
|
1395
|
+
JSON.stringify({ type: "workspace_sync", worker: input.workerLabel, attemptId, detail: input.commitDetail ?? null })
|
|
1396
|
+
],
|
|
1397
|
+
"commit workspace working trees"
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
433
1401
|
if (!input.skipMountMirror) {
|
|
434
|
-
|
|
435
|
-
|
|
1402
|
+
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1403
|
+
try {
|
|
1404
|
+
mirrorMountsToWorkspace(workspacePath, projectedActiveMounts);
|
|
1405
|
+
removeWorkspaceMounts(workspacePath, projectedTombstones);
|
|
1406
|
+
restoreDeferredWorkspaceMountsFromHead(
|
|
1407
|
+
workspacePath,
|
|
1408
|
+
input.mounts.filter(({ id }) => deferredMountIds.has(id))
|
|
1409
|
+
);
|
|
1410
|
+
stageAndCommitWorkspace();
|
|
1411
|
+
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
1414
|
+
}
|
|
1415
|
+
} else {
|
|
1416
|
+
stageAndCommitWorkspace();
|
|
1417
|
+
if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
|
|
436
1418
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
[
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
],
|
|
447
|
-
"commit workspace working trees"
|
|
448
|
-
);
|
|
1419
|
+
if (!input.skipMountMirror) {
|
|
1420
|
+
const projectedHead = revParse(workspacePath, "HEAD");
|
|
1421
|
+
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1422
|
+
const targetProjectionReceipt = workspaceHydrationReceipt(projectedHead, projectionReceiptMounts);
|
|
1423
|
+
if (!workspaceHydrationReceiptsEqual(readHydratedWorkspaceReceipt(workspacePath), targetProjectionReceipt)) {
|
|
1424
|
+
fsyncHydratedWorkspaceMounts([...projectedActiveMounts, ...projectedTombstones]);
|
|
1425
|
+
updateHydratedWorkspaceReceipt(workspacePath, targetProjectionReceipt);
|
|
1426
|
+
}
|
|
1427
|
+
completeWorkspaceCheckoutTransition(workspacePath);
|
|
449
1428
|
}
|
|
450
1429
|
let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
451
1430
|
let rebaseCount = 0;
|
|
@@ -463,8 +1442,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
463
1442
|
rebaseCount: rebaseCount + 1,
|
|
464
1443
|
diffSizeBytes: 0,
|
|
465
1444
|
affectedPaths: [.../* @__PURE__ */ new Set([...newlyStagedPaths, ...reconciled.conflictPaths])].sort(),
|
|
466
|
-
activeMountIds: [...
|
|
467
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort(),
|
|
1445
|
+
activeMountIds: [...projectedActiveMounts, ...projectedTombstones].map(({ id }) => id).sort(),
|
|
1446
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...deferredMountIds])].sort(),
|
|
468
1447
|
conflictPaths: reconciled.conflictPaths,
|
|
469
1448
|
conflictSnapshotRefs: reconciled.refs,
|
|
470
1449
|
error: reconciled.error
|
|
@@ -472,6 +1451,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
472
1451
|
}
|
|
473
1452
|
updated ||= reconciled.updated;
|
|
474
1453
|
if (reconciled.rebased) rebaseCount += 1;
|
|
1454
|
+
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
475
1455
|
const localHead = revParse(workspacePath, "HEAD");
|
|
476
1456
|
if (!localHead) {
|
|
477
1457
|
return {
|
|
@@ -505,11 +1485,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
505
1485
|
};
|
|
506
1486
|
}
|
|
507
1487
|
if (localHead === remoteHead) {
|
|
1488
|
+
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
508
1489
|
await input.afterWorkspacePublished?.({
|
|
509
1490
|
publishedHead: localHead,
|
|
510
|
-
activeMountIds:
|
|
1491
|
+
activeMountIds: completedMounts.activeMountIds
|
|
511
1492
|
});
|
|
512
|
-
if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
|
|
513
1493
|
return {
|
|
514
1494
|
outcome: updated ? "updated" : "no_change",
|
|
515
1495
|
startingHead,
|
|
@@ -519,8 +1499,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
519
1499
|
rebaseCount,
|
|
520
1500
|
diffSizeBytes: size,
|
|
521
1501
|
affectedPaths: paths,
|
|
522
|
-
|
|
523
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort()
|
|
1502
|
+
...completedMounts
|
|
524
1503
|
};
|
|
525
1504
|
}
|
|
526
1505
|
const pushArgs = [
|
|
@@ -533,11 +1512,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
533
1512
|
const push = gitResult(workspacePath, pushArgs);
|
|
534
1513
|
if (push.exitCode === 0) {
|
|
535
1514
|
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1515
|
+
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
536
1516
|
await input.afterWorkspacePublished?.({
|
|
537
1517
|
publishedHead: localHead,
|
|
538
|
-
activeMountIds:
|
|
1518
|
+
activeMountIds: completedMounts.activeMountIds
|
|
539
1519
|
});
|
|
540
|
-
if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
|
|
541
1520
|
return {
|
|
542
1521
|
outcome: "pushed",
|
|
543
1522
|
startingHead,
|
|
@@ -547,8 +1526,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
547
1526
|
rebaseCount,
|
|
548
1527
|
diffSizeBytes: size,
|
|
549
1528
|
affectedPaths: paths,
|
|
550
|
-
|
|
551
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort()
|
|
1529
|
+
...completedMounts
|
|
552
1530
|
};
|
|
553
1531
|
}
|
|
554
1532
|
if (!/(non-fast-forward|fetch first|rejected|failed to push some refs)/i.test(`${push.stderr}
|
|
@@ -570,10 +1548,15 @@ const workspaceGitSyncTestHarness = {
|
|
|
570
1548
|
0 && (module.exports = {
|
|
571
1549
|
MAX_WORKSPACE_GIT_DIFF_BYTES,
|
|
572
1550
|
WORKSPACE_GIT_BRANCH,
|
|
1551
|
+
WORKSPACE_GIT_CHECKOUT_DURABILITY,
|
|
573
1552
|
WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
|
|
1553
|
+
WORKSPACE_GIT_HYDRATED_RECEIPT,
|
|
1554
|
+
WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
574
1555
|
ensureWorkspaceGitClone,
|
|
575
1556
|
hydrateWorkspaceGitMounts,
|
|
1557
|
+
recoverWorkspaceGitHydration,
|
|
576
1558
|
resetWorkspaceGit,
|
|
577
1559
|
synchronizeWorkspaceGit,
|
|
1560
|
+
workspaceGitHydrationIsCurrent,
|
|
578
1561
|
workspaceGitSyncTestHarness
|
|
579
1562
|
});
|