@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
|
@@ -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,631 @@ 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)({ sourceRoot: sourcePath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
id: mount.id,
|
|
652
|
+
incarnationKey: mount.hydrationIncarnationKey,
|
|
653
|
+
sourcePath,
|
|
654
|
+
durabilityRootPath,
|
|
655
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
656
|
+
existed: Boolean(sourceStatus),
|
|
657
|
+
snapshotName
|
|
658
|
+
};
|
|
659
|
+
})
|
|
660
|
+
};
|
|
661
|
+
writePrivateFileDurably(import_node_path.default.join(stagingPath, "manifest.json"), `${JSON.stringify(manifest)}
|
|
662
|
+
`);
|
|
663
|
+
fsyncTree(stagingPath);
|
|
664
|
+
import_node_fs.default.renameSync(stagingPath, transactionPath);
|
|
665
|
+
fsyncDirectory(stateDirectory);
|
|
666
|
+
return manifest;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
import_node_fs.default.rmSync(stagingPath, { recursive: true, force: true });
|
|
669
|
+
fsyncDirectory(stateDirectory);
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function markHydrationTransactionDurable(workspacePath) {
|
|
674
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
675
|
+
writePrivateFileDurably(import_node_path.default.join(transactionPath, "committed"), "committed\n");
|
|
676
|
+
fsyncDirectory(transactionPath);
|
|
677
|
+
}
|
|
678
|
+
function fsyncHydratedWorkspaceMount(mount) {
|
|
679
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
680
|
+
trustedRoot: mount.durabilityRootPath,
|
|
681
|
+
candidate: mount.sourcePath,
|
|
682
|
+
label: `Workspace mount ${mount.id} source`
|
|
683
|
+
});
|
|
684
|
+
const syncEntry = (entryPath, root) => {
|
|
685
|
+
const status = import_node_fs.default.lstatSync(entryPath);
|
|
686
|
+
if (status.isSymbolicLink()) return;
|
|
687
|
+
if (status.isDirectory()) {
|
|
688
|
+
for (const name of import_node_fs.default.readdirSync(entryPath)) {
|
|
689
|
+
if (root && name === ".git") continue;
|
|
690
|
+
syncEntry(import_node_path.default.join(entryPath, name), false);
|
|
691
|
+
}
|
|
692
|
+
fsyncDirectory(entryPath);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (!status.isFile()) return;
|
|
696
|
+
const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY);
|
|
697
|
+
try {
|
|
698
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
699
|
+
} finally {
|
|
700
|
+
import_node_fs.default.closeSync(descriptor);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
if (import_node_fs.default.existsSync(mount.sourcePath)) syncEntry(mount.sourcePath, true);
|
|
704
|
+
const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
705
|
+
let boundaryParent = import_node_path.default.dirname(import_node_path.default.resolve(mount.sourcePath));
|
|
706
|
+
while (true) {
|
|
707
|
+
if (import_node_fs.default.existsSync(boundaryParent)) {
|
|
708
|
+
const boundaryStatus = import_node_fs.default.lstatSync(boundaryParent);
|
|
709
|
+
if (!boundaryStatus.isDirectory() || boundaryStatus.isSymbolicLink()) {
|
|
710
|
+
throw new Error(`Workspace mount durability boundary is not a regular directory: ${boundaryParent}`);
|
|
711
|
+
}
|
|
712
|
+
fsyncDirectory(boundaryParent);
|
|
713
|
+
}
|
|
714
|
+
if (boundaryParent === durabilityRoot) break;
|
|
715
|
+
const nextParent = import_node_path.default.dirname(boundaryParent);
|
|
716
|
+
if (nextParent === boundaryParent) {
|
|
717
|
+
throw new Error(`Workspace mount ${mount.sourcePath} does not descend from durability root ${durabilityRoot}`);
|
|
718
|
+
}
|
|
719
|
+
boundaryParent = nextParent;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function fsyncHydratedWorkspaceMounts(mounts) {
|
|
723
|
+
for (const mount of mounts) fsyncHydratedWorkspaceMount(mount);
|
|
724
|
+
}
|
|
91
725
|
function workspaceIsShallow(workspacePath) {
|
|
92
726
|
return gitResult(workspacePath, ["rev-parse", "--is-shallow-repository"]).stdout === "true";
|
|
93
727
|
}
|
|
@@ -100,7 +734,22 @@ function validateMounts(workspacePath, mounts) {
|
|
|
100
734
|
const paths = [];
|
|
101
735
|
for (const mount of mounts) {
|
|
102
736
|
if (!mount.id || ids.has(mount.id)) throw new Error(`Workspace mount id must be unique: ${mount.id}`);
|
|
737
|
+
if (!mount.hydrationIncarnationKey || mount.hydrationIncarnationKey.includes("\0")) {
|
|
738
|
+
throw new Error(`Workspace mount hydration incarnation is invalid: ${mount.id}`);
|
|
739
|
+
}
|
|
103
740
|
ids.add(mount.id);
|
|
741
|
+
const sourcePath = import_node_path.default.resolve(mount.sourcePath);
|
|
742
|
+
const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
743
|
+
const durabilityRelative = import_node_path.default.relative(durabilityRoot, sourcePath);
|
|
744
|
+
const durabilityRootStatus = import_node_fs.default.lstatSync(durabilityRoot);
|
|
745
|
+
if (!durabilityRootStatus.isDirectory() || durabilityRootStatus.isSymbolicLink() || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative)) {
|
|
746
|
+
throw new Error(`Workspace mount source escapes its durability root: ${mount.sourcePath}`);
|
|
747
|
+
}
|
|
748
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
749
|
+
trustedRoot: durabilityRoot,
|
|
750
|
+
candidate: sourcePath,
|
|
751
|
+
label: `Workspace mount ${mount.id} source`
|
|
752
|
+
});
|
|
104
753
|
const normalized = mount.workspaceRelativePath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
105
754
|
if (!normalized || normalized === "." || normalized.split("/").some((segment) => segment === ".." || segment === ".git")) {
|
|
106
755
|
throw new Error(`Invalid workspace mount path: ${mount.workspaceRelativePath}`);
|
|
@@ -147,6 +796,8 @@ function configureWorkspaceRepository(input) {
|
|
|
147
796
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
148
797
|
git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
|
|
149
798
|
git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
|
|
799
|
+
git(input.workspacePath, ["config", "--local", "core.fsync", "committed"], "configure durable workspace commits");
|
|
800
|
+
git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
150
801
|
}
|
|
151
802
|
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
152
803
|
const result = gitResult(workspacePath, [
|
|
@@ -170,7 +821,24 @@ ${result.stdout}`;
|
|
|
170
821
|
}
|
|
171
822
|
function ensureWorkspaceGitClone(input) {
|
|
172
823
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
824
|
+
const workspaceParent = import_node_path.default.dirname(workspacePath);
|
|
825
|
+
const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
|
|
826
|
+
if (!parentStatus.isDirectory() || parentStatus.isSymbolicLink()) {
|
|
827
|
+
throw new Error(`Workspace clone parent is not a regular directory: ${workspaceParent}`);
|
|
828
|
+
}
|
|
829
|
+
const workspaceStatus = lstatIfExists(workspacePath);
|
|
830
|
+
if (workspaceStatus) {
|
|
831
|
+
if (!workspaceStatus.isDirectory() || workspaceStatus.isSymbolicLink()) {
|
|
832
|
+
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
173
835
|
const gitPath = import_node_path.default.join(workspacePath, ".git");
|
|
836
|
+
const gitStatus = lstatIfExists(gitPath);
|
|
837
|
+
if (gitStatus) {
|
|
838
|
+
if (!gitStatus.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
839
|
+
throw new Error(`Workspace clone Git metadata is not a regular directory: ${gitPath}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
174
842
|
if (!import_node_fs.default.existsSync(gitPath)) {
|
|
175
843
|
import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
|
|
176
844
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(workspacePath), { recursive: true });
|
|
@@ -192,6 +860,7 @@ function ensureWorkspaceGitClone(input) {
|
|
|
192
860
|
}
|
|
193
861
|
}
|
|
194
862
|
configureWorkspaceRepository({ ...input, workspacePath });
|
|
863
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
195
864
|
const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
196
865
|
const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
|
|
197
866
|
if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
@@ -200,17 +869,21 @@ function ensureWorkspaceGitClone(input) {
|
|
|
200
869
|
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
201
870
|
let localHead = revParse(workspacePath, "HEAD");
|
|
202
871
|
if (!localHead && remoteHead) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
872
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
873
|
+
git(
|
|
874
|
+
workspacePath,
|
|
875
|
+
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
876
|
+
"check out workspace main"
|
|
877
|
+
);
|
|
878
|
+
});
|
|
208
879
|
localHead = remoteHead;
|
|
209
880
|
}
|
|
210
881
|
if (localHead && remoteHead && localHead === remoteHead) {
|
|
211
882
|
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
212
883
|
} else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
|
|
213
|
-
|
|
884
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
885
|
+
git(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
|
|
886
|
+
});
|
|
214
887
|
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
215
888
|
localHead = remoteHead;
|
|
216
889
|
}
|
|
@@ -230,6 +903,16 @@ function activeMounts(mounts) {
|
|
|
230
903
|
}
|
|
231
904
|
function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
232
905
|
for (const mount of mounts) {
|
|
906
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
907
|
+
trustedRoot: mount.durabilityRootPath,
|
|
908
|
+
candidate: mount.sourcePath,
|
|
909
|
+
label: `Workspace mount ${mount.id} source`
|
|
910
|
+
});
|
|
911
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
912
|
+
trustedRoot: workspacePath,
|
|
913
|
+
candidate: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
914
|
+
label: `Workspace mount ${mount.id} outer target`
|
|
915
|
+
});
|
|
233
916
|
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
234
917
|
sourceRoot: mount.sourcePath,
|
|
235
918
|
targetRoot: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
@@ -241,6 +924,11 @@ function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
|
241
924
|
function removeWorkspaceMounts(workspacePath, mounts) {
|
|
242
925
|
for (const mount of mounts) {
|
|
243
926
|
const targetPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
927
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
928
|
+
trustedRoot: workspacePath,
|
|
929
|
+
candidate: targetPath,
|
|
930
|
+
label: `Workspace mount ${mount.id} outer target`
|
|
931
|
+
});
|
|
244
932
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(workspacePath), import_node_path.default.resolve(targetPath));
|
|
245
933
|
if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
246
934
|
throw new Error(`Workspace mount escapes the workspace clone: ${mount.workspaceRelativePath}`);
|
|
@@ -248,9 +936,45 @@ function removeWorkspaceMounts(workspacePath, mounts) {
|
|
|
248
936
|
import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
|
|
249
937
|
}
|
|
250
938
|
}
|
|
251
|
-
function
|
|
939
|
+
function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
|
|
940
|
+
const head = revParse(workspacePath, "HEAD");
|
|
941
|
+
for (const mount of mounts) {
|
|
942
|
+
const normalized = normalizedWorkspaceMountPath(mount.workspaceRelativePath);
|
|
943
|
+
const targetPath = import_node_path.default.join(workspacePath, ...normalized.split("/"));
|
|
944
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
945
|
+
trustedRoot: workspacePath,
|
|
946
|
+
candidate: targetPath,
|
|
947
|
+
label: `Deferred workspace mount ${mount.id} outer target`
|
|
948
|
+
});
|
|
949
|
+
import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
|
|
950
|
+
if (head && tryGit(workspacePath, ["cat-file", "-e", `${head}:${normalized}`])) {
|
|
951
|
+
git(
|
|
952
|
+
workspacePath,
|
|
953
|
+
["checkout", "--no-recurse-submodules", head, "--", `:(literal)${normalized}`],
|
|
954
|
+
`restore deferred workspace mount ${mount.id} from its integration basis`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
960
|
+
const hydratedMountIds = [];
|
|
961
|
+
const skippedMountIds = [];
|
|
252
962
|
for (const mount of mounts) {
|
|
963
|
+
if (!options.ignoreBusy && mount.busy?.()) {
|
|
964
|
+
skippedMountIds.push(mount.id);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
253
967
|
const sourceRoot = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
968
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
969
|
+
trustedRoot: workspacePath,
|
|
970
|
+
candidate: sourceRoot,
|
|
971
|
+
label: `Workspace mount ${mount.id} outer source`
|
|
972
|
+
});
|
|
973
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
974
|
+
trustedRoot: mount.durabilityRootPath,
|
|
975
|
+
candidate: mount.sourcePath,
|
|
976
|
+
label: `Workspace mount ${mount.id} hydration target`
|
|
977
|
+
});
|
|
254
978
|
if (!import_node_fs.default.existsSync(sourceRoot)) {
|
|
255
979
|
if (mount.hydrateDeletionMode === "all") {
|
|
256
980
|
import_node_fs.default.rmSync(mount.sourcePath, { recursive: true, force: true });
|
|
@@ -267,37 +991,167 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts) {
|
|
|
267
991
|
import_node_fs.default.rmSync(emptyRoot, { recursive: true, force: true });
|
|
268
992
|
}
|
|
269
993
|
}
|
|
994
|
+
hydratedMountIds.push(mount.id);
|
|
270
995
|
continue;
|
|
271
996
|
}
|
|
997
|
+
const targetExists = import_node_fs.default.existsSync(mount.sourcePath);
|
|
272
998
|
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
273
999
|
sourceRoot,
|
|
274
1000
|
targetRoot: mount.sourcePath,
|
|
275
1001
|
sourceMode: "all",
|
|
276
|
-
|
|
1002
|
+
// A missing checkout has no Git index to define git-only deletions. Its
|
|
1003
|
+
// newly created directory is empty, so all-mode is equivalent and lets
|
|
1004
|
+
// bootstrap hydration preserve the fetched tree before Git initializes.
|
|
1005
|
+
deletionMode: targetExists ? mount.hydrateDeletionMode : "all"
|
|
277
1006
|
});
|
|
1007
|
+
hydratedMountIds.push(mount.id);
|
|
1008
|
+
}
|
|
1009
|
+
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
|
|
1010
|
+
}
|
|
1011
|
+
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1012
|
+
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1013
|
+
const mountIds = input.mounts.map(({ id }) => id).sort();
|
|
1014
|
+
const requiredIds = (input.requiredMounts ?? input.mounts).map(({ id }) => id).sort();
|
|
1015
|
+
if (mountIds.length !== requiredIds.length || mountIds.some((id, index) => id !== requiredIds[index])) {
|
|
1016
|
+
return { hydratedMountIds: [], skippedMountIds: requiredIds };
|
|
1017
|
+
}
|
|
1018
|
+
const busyMountIds = input.ignoreBusy ? [] : (input.durabilityMounts ?? input.mounts).filter((mount) => mount.busy?.()).map(({ id }) => id).sort();
|
|
1019
|
+
if (busyMountIds.length > 0) {
|
|
1020
|
+
return { hydratedMountIds: [], skippedMountIds: busyMountIds };
|
|
1021
|
+
}
|
|
1022
|
+
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1023
|
+
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1024
|
+
}
|
|
1025
|
+
const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
|
|
1026
|
+
const targetReceipt = targetHead ? workspaceHydrationReceipt(targetHead, input.receiptMounts ?? input.durabilityMounts ?? input.mounts) : null;
|
|
1027
|
+
const manifest = beginHydrationTransaction(workspacePath, input.mounts, targetReceipt);
|
|
1028
|
+
try {
|
|
1029
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, input.mounts, { ignoreBusy: true });
|
|
1030
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== requiredIds.length || hydration.hydratedMountIds.some((id, index) => id !== requiredIds[index])) {
|
|
1031
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1032
|
+
}
|
|
1033
|
+
fsyncHydratedWorkspaceMounts(input.durabilityMounts ?? input.mounts);
|
|
1034
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1035
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1036
|
+
removeHydrationTransaction(workspacePath);
|
|
1037
|
+
return hydration;
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1040
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1041
|
+
throw error;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1045
|
+
removeHydrationTransaction(workspacePath);
|
|
1046
|
+
} catch (restoreError) {
|
|
1047
|
+
throw new AggregateError(
|
|
1048
|
+
[error, restoreError],
|
|
1049
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1056
|
+
const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
|
|
1057
|
+
if (!import_node_fs.default.existsSync(gitDirectory)) {
|
|
1058
|
+
return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
|
|
1059
|
+
}
|
|
1060
|
+
recoverWorkspaceCheckoutDurability(import_node_path.default.resolve(workspacePath));
|
|
1061
|
+
return hydrateWorkspaceGitMountsTransactionally({
|
|
1062
|
+
workspacePath,
|
|
1063
|
+
mounts,
|
|
1064
|
+
ignoreBusy: options.ignoreBusy
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1068
|
+
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
1069
|
+
validateMounts(workspacePath, mounts);
|
|
1070
|
+
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
1071
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
1072
|
+
const currentHead = revParse(workspacePath, "HEAD");
|
|
1073
|
+
if (!currentHead) return;
|
|
1074
|
+
const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1075
|
+
const outerHeadContainsMount = (mount) => tryGit(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1076
|
+
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1077
|
+
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1078
|
+
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
1079
|
+
);
|
|
1080
|
+
if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
|
|
1081
|
+
const busyMountIds = options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1082
|
+
if (busyMountIds.length > 0) {
|
|
1083
|
+
throw new Error(
|
|
1084
|
+
`Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${busyMountIds.join(", ")}`
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const hydrationTargets = uncoveredBasisMounts.filter(
|
|
1088
|
+
(mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
|
|
1089
|
+
);
|
|
1090
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1091
|
+
workspacePath,
|
|
1092
|
+
mounts: hydrationTargets,
|
|
1093
|
+
durabilityMounts: uncoveredBasisMounts,
|
|
1094
|
+
receiptMounts: configuredBasisMounts,
|
|
1095
|
+
requiredMounts: hydrationTargets,
|
|
1096
|
+
ignoreBusy: options.ignoreBusy,
|
|
1097
|
+
recordCurrentHead: true
|
|
1098
|
+
});
|
|
1099
|
+
if (hydration.skippedMountIds.length > 0) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${hydration.skippedMountIds.join(", ")}`
|
|
1102
|
+
);
|
|
278
1103
|
}
|
|
279
1104
|
}
|
|
280
1105
|
function resetWorkspaceGit(input) {
|
|
281
1106
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
282
1107
|
validateMounts(workspacePath, input.mounts);
|
|
283
1108
|
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1109
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
284
1110
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
285
1111
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
286
1112
|
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
287
1113
|
if (remoteHead) {
|
|
288
|
-
|
|
289
|
-
|
|
1114
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1115
|
+
git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
|
|
1116
|
+
git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
|
|
1117
|
+
});
|
|
290
1118
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
291
|
-
|
|
292
|
-
|
|
1119
|
+
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1120
|
+
git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
|
|
1121
|
+
git(workspacePath, ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })], "record empty workspace reset");
|
|
1122
|
+
});
|
|
293
1123
|
}
|
|
294
1124
|
const selected = activeMounts(input.mounts);
|
|
295
|
-
|
|
1125
|
+
const requiredLiveMountsBeforeHydration = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
1126
|
+
const bootstrapMounts = selected.skipped.filter((mount) => {
|
|
1127
|
+
if (mount.deleteWhenSourceMissing || import_node_fs.default.existsSync(mount.sourcePath) || mount.busy?.()) return false;
|
|
1128
|
+
const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1129
|
+
return import_node_fs.default.existsSync(outerPath);
|
|
1130
|
+
});
|
|
1131
|
+
const hydrationMounts = [...selected.active, ...bootstrapMounts];
|
|
1132
|
+
const requiredLiveMounts = [
|
|
1133
|
+
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
1134
|
+
];
|
|
1135
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1136
|
+
workspacePath,
|
|
1137
|
+
mounts: hydrationMounts,
|
|
1138
|
+
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
|
|
1139
|
+
receiptMounts: input.mounts,
|
|
1140
|
+
requiredMounts: requiredLiveMounts,
|
|
1141
|
+
recordCurrentHead: true
|
|
1142
|
+
});
|
|
1143
|
+
const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
|
|
1144
|
+
const resetHead = revParse(workspacePath, "HEAD");
|
|
1145
|
+
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)) {
|
|
1146
|
+
throw new Error("Workspace reset could not durably hydrate every live mount; remediation remains active");
|
|
1147
|
+
}
|
|
296
1148
|
return {
|
|
297
1149
|
startingHead: initial.localHead,
|
|
298
1150
|
localHead: revParse(workspacePath, "HEAD"),
|
|
299
1151
|
remoteHead,
|
|
300
|
-
discardedPaths
|
|
1152
|
+
discardedPaths,
|
|
1153
|
+
activeMountIds: hydration.hydratedMountIds,
|
|
1154
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
301
1155
|
};
|
|
302
1156
|
}
|
|
303
1157
|
function stagedPaths(workspacePath) {
|
|
@@ -362,17 +1216,22 @@ function snapshotConflict(input) {
|
|
|
362
1216
|
}
|
|
363
1217
|
function rebaseWorkspace(input) {
|
|
364
1218
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
1219
|
+
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
365
1220
|
const rebase = gitResult(
|
|
366
1221
|
input.workspacePath,
|
|
367
1222
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
368
1223
|
);
|
|
369
|
-
if (rebase.exitCode === 0)
|
|
1224
|
+
if (rebase.exitCode === 0) {
|
|
1225
|
+
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1226
|
+
return { ok: true };
|
|
1227
|
+
}
|
|
370
1228
|
const conflicts = conflictPaths(input.workspacePath);
|
|
371
1229
|
const refs = snapshotConflict(input);
|
|
372
1230
|
const abort = gitResult(input.workspacePath, ["rebase", "--abort"]);
|
|
373
1231
|
if (abort.exitCode !== 0) {
|
|
374
1232
|
throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
375
1233
|
}
|
|
1234
|
+
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
376
1235
|
return {
|
|
377
1236
|
ok: false,
|
|
378
1237
|
conflictPaths: conflicts,
|
|
@@ -382,26 +1241,31 @@ function rebaseWorkspace(input) {
|
|
|
382
1241
|
}
|
|
383
1242
|
function synchronizeWithFetchedHead(input) {
|
|
384
1243
|
const localHead = revParse(input.workspacePath, "HEAD");
|
|
385
|
-
|
|
1244
|
+
const remoteHead = input.remoteHead;
|
|
1245
|
+
if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
|
|
386
1246
|
if (!localHead) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1247
|
+
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1248
|
+
git(
|
|
1249
|
+
input.workspacePath,
|
|
1250
|
+
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1251
|
+
"check out fetched workspace main"
|
|
1252
|
+
);
|
|
1253
|
+
});
|
|
392
1254
|
return { kind: "ready", updated: true, rebased: false };
|
|
393
1255
|
}
|
|
394
|
-
if (localHead ===
|
|
395
|
-
updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1256
|
+
if (localHead === remoteHead) {
|
|
1257
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
396
1258
|
return { kind: "ready", updated: false, rebased: false };
|
|
397
1259
|
}
|
|
398
|
-
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead,
|
|
399
|
-
|
|
400
|
-
|
|
1260
|
+
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
|
|
1261
|
+
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1262
|
+
git(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
|
|
1263
|
+
});
|
|
1264
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
401
1265
|
return { kind: "ready", updated: true, rebased: false };
|
|
402
1266
|
}
|
|
403
|
-
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor",
|
|
404
|
-
updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1267
|
+
if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
|
|
1268
|
+
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
405
1269
|
return { kind: "ready", updated: false, rebased: false };
|
|
406
1270
|
}
|
|
407
1271
|
const integratedHead = revParse(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
|
|
@@ -414,11 +1278,11 @@ function synchronizeWithFetchedHead(input) {
|
|
|
414
1278
|
const rebased = rebaseWorkspace({
|
|
415
1279
|
workspacePath: input.workspacePath,
|
|
416
1280
|
localHead,
|
|
417
|
-
remoteHead
|
|
1281
|
+
remoteHead,
|
|
418
1282
|
attemptId: input.attemptId,
|
|
419
1283
|
truncatedHistoryBase
|
|
420
1284
|
});
|
|
421
|
-
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath,
|
|
1285
|
+
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
422
1286
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
423
1287
|
}
|
|
424
1288
|
async function synchronizeWorkspaceGit(input) {
|
|
@@ -427,25 +1291,131 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
427
1291
|
const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
|
|
428
1292
|
const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
|
|
429
1293
|
validateMounts(workspacePath, input.mounts);
|
|
1294
|
+
const busyLiveMountIds = input.skipMountMirror ? [] : input.mounts.filter((mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()).map(({ id }) => id).sort();
|
|
1295
|
+
if (busyLiveMountIds.length > 0) {
|
|
1296
|
+
const cloneExists = import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git"));
|
|
1297
|
+
const localHead = cloneExists ? revParse(workspacePath, "HEAD") : null;
|
|
1298
|
+
const remoteHead2 = cloneExists ? revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`) : null;
|
|
1299
|
+
return {
|
|
1300
|
+
outcome: "no_change",
|
|
1301
|
+
startingHead: localHead,
|
|
1302
|
+
localHead,
|
|
1303
|
+
remoteHead: remoteHead2,
|
|
1304
|
+
publishedHead: remoteHead2,
|
|
1305
|
+
rebaseCount: 0,
|
|
1306
|
+
diffSizeBytes: 0,
|
|
1307
|
+
affectedPaths: [],
|
|
1308
|
+
activeMountIds: [],
|
|
1309
|
+
skippedMountIds: input.mounts.map(({ id }) => id).sort()
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
430
1312
|
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
431
1313
|
const startingHead = initial.localHead;
|
|
1314
|
+
const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1315
|
+
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1316
|
+
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead && receiptBeforeInitialHydration?.head === initial.localHead) {
|
|
1317
|
+
for (const mount of input.mounts) {
|
|
1318
|
+
if (!mount.deleteWhenSourceMissing && mount.preserveLocalOnInitialOuterAbsence !== true && mount.preserveLocalOnHydrationBasisChange !== true && !workspaceHydrationReceiptCoversMount(receiptBeforeInitialHydration, initial.localHead, mount)) {
|
|
1319
|
+
deferredMountIds.add(mount.id);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1324
|
+
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {}
|
|
1325
|
+
});
|
|
1326
|
+
const receiptRequiredLiveMounts = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
432
1327
|
const selected = activeMounts(input.mounts);
|
|
1328
|
+
const projectedActiveMounts = selected.active.filter(({ id }) => !deferredMountIds.has(id));
|
|
1329
|
+
const projectedTombstones = selected.tombstones.filter(({ id }) => !deferredMountIds.has(id));
|
|
1330
|
+
const projectionBasisHead = revParse(workspacePath, "HEAD");
|
|
1331
|
+
const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1332
|
+
const projectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts).filter(
|
|
1333
|
+
(mount) => !deferredMountIds.has(mount.id) && Boolean(projectionBasisHead && workspaceHydrationReceiptCoversMount(projectionBasisReceipt, projectionBasisHead, mount))
|
|
1334
|
+
);
|
|
1335
|
+
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1336
|
+
const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
|
|
1337
|
+
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1338
|
+
if (currentHeadBeforeHydration && workspaceHydrationReceiptMatchesMounts(
|
|
1339
|
+
readHydratedWorkspaceReceipt(workspacePath),
|
|
1340
|
+
currentHeadBeforeHydration,
|
|
1341
|
+
completionReceiptMounts
|
|
1342
|
+
)) {
|
|
1343
|
+
const lateBusyIds = selected.active.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1344
|
+
const lateBusy = new Set(lateBusyIds);
|
|
1345
|
+
return {
|
|
1346
|
+
activeMountIds: [...selected.active.filter(({ id }) => !lateBusy.has(id)), ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1347
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...lateBusyIds])].sort()
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1351
|
+
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1352
|
+
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
1353
|
+
);
|
|
1354
|
+
const uncoveredCompletionMounts = selected.active.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1355
|
+
const uncoveredRequiredLiveMounts = receiptRequiredLiveMounts.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1356
|
+
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter((mount) => !mountIsAlreadyCovered(mount));
|
|
1357
|
+
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1358
|
+
workspacePath,
|
|
1359
|
+
mounts: uncoveredCompletionMounts,
|
|
1360
|
+
durabilityMounts: uncoveredCompletionBasisMounts,
|
|
1361
|
+
receiptMounts: completionReceiptMounts,
|
|
1362
|
+
requiredMounts: uncoveredRequiredLiveMounts,
|
|
1363
|
+
recordCurrentHead: true
|
|
1364
|
+
});
|
|
1365
|
+
if (requireCurrentHeadReceipt) {
|
|
1366
|
+
const currentHead = revParse(workspacePath, "HEAD");
|
|
1367
|
+
if (currentHead && !workspaceHydrationReceiptMatchesMounts(readHydratedWorkspaceReceipt(workspacePath), currentHead, completionReceiptMounts)) {
|
|
1368
|
+
throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1373
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1374
|
+
};
|
|
1375
|
+
};
|
|
1376
|
+
let newlyStagedPaths = [];
|
|
1377
|
+
const stageAndCommitWorkspace = () => {
|
|
1378
|
+
git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1379
|
+
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1380
|
+
if (newlyStagedPaths.length > 0) {
|
|
1381
|
+
git(
|
|
1382
|
+
workspacePath,
|
|
1383
|
+
[
|
|
1384
|
+
"commit",
|
|
1385
|
+
"-m",
|
|
1386
|
+
JSON.stringify({ type: "workspace_sync", worker: input.workerLabel, attemptId, detail: input.commitDetail ?? null })
|
|
1387
|
+
],
|
|
1388
|
+
"commit workspace working trees"
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
433
1392
|
if (!input.skipMountMirror) {
|
|
434
|
-
|
|
435
|
-
|
|
1393
|
+
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1394
|
+
try {
|
|
1395
|
+
mirrorMountsToWorkspace(workspacePath, projectedActiveMounts);
|
|
1396
|
+
removeWorkspaceMounts(workspacePath, projectedTombstones);
|
|
1397
|
+
restoreDeferredWorkspaceMountsFromHead(
|
|
1398
|
+
workspacePath,
|
|
1399
|
+
input.mounts.filter(({ id }) => deferredMountIds.has(id))
|
|
1400
|
+
);
|
|
1401
|
+
stageAndCommitWorkspace();
|
|
1402
|
+
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
1405
|
+
}
|
|
1406
|
+
} else {
|
|
1407
|
+
stageAndCommitWorkspace();
|
|
1408
|
+
if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
|
|
436
1409
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
[
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
],
|
|
447
|
-
"commit workspace working trees"
|
|
448
|
-
);
|
|
1410
|
+
if (!input.skipMountMirror) {
|
|
1411
|
+
const projectedHead = revParse(workspacePath, "HEAD");
|
|
1412
|
+
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1413
|
+
const targetProjectionReceipt = workspaceHydrationReceipt(projectedHead, projectionReceiptMounts);
|
|
1414
|
+
if (!workspaceHydrationReceiptsEqual(readHydratedWorkspaceReceipt(workspacePath), targetProjectionReceipt)) {
|
|
1415
|
+
fsyncHydratedWorkspaceMounts([...projectedActiveMounts, ...projectedTombstones]);
|
|
1416
|
+
updateHydratedWorkspaceReceipt(workspacePath, targetProjectionReceipt);
|
|
1417
|
+
}
|
|
1418
|
+
completeWorkspaceCheckoutTransition(workspacePath);
|
|
449
1419
|
}
|
|
450
1420
|
let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
451
1421
|
let rebaseCount = 0;
|
|
@@ -463,8 +1433,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
463
1433
|
rebaseCount: rebaseCount + 1,
|
|
464
1434
|
diffSizeBytes: 0,
|
|
465
1435
|
affectedPaths: [.../* @__PURE__ */ new Set([...newlyStagedPaths, ...reconciled.conflictPaths])].sort(),
|
|
466
|
-
activeMountIds: [...
|
|
467
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort(),
|
|
1436
|
+
activeMountIds: [...projectedActiveMounts, ...projectedTombstones].map(({ id }) => id).sort(),
|
|
1437
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...deferredMountIds])].sort(),
|
|
468
1438
|
conflictPaths: reconciled.conflictPaths,
|
|
469
1439
|
conflictSnapshotRefs: reconciled.refs,
|
|
470
1440
|
error: reconciled.error
|
|
@@ -472,6 +1442,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
472
1442
|
}
|
|
473
1443
|
updated ||= reconciled.updated;
|
|
474
1444
|
if (reconciled.rebased) rebaseCount += 1;
|
|
1445
|
+
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
475
1446
|
const localHead = revParse(workspacePath, "HEAD");
|
|
476
1447
|
if (!localHead) {
|
|
477
1448
|
return {
|
|
@@ -505,11 +1476,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
505
1476
|
};
|
|
506
1477
|
}
|
|
507
1478
|
if (localHead === remoteHead) {
|
|
1479
|
+
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
508
1480
|
await input.afterWorkspacePublished?.({
|
|
509
1481
|
publishedHead: localHead,
|
|
510
|
-
activeMountIds:
|
|
1482
|
+
activeMountIds: completedMounts.activeMountIds
|
|
511
1483
|
});
|
|
512
|
-
if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
|
|
513
1484
|
return {
|
|
514
1485
|
outcome: updated ? "updated" : "no_change",
|
|
515
1486
|
startingHead,
|
|
@@ -519,8 +1490,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
519
1490
|
rebaseCount,
|
|
520
1491
|
diffSizeBytes: size,
|
|
521
1492
|
affectedPaths: paths,
|
|
522
|
-
|
|
523
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort()
|
|
1493
|
+
...completedMounts
|
|
524
1494
|
};
|
|
525
1495
|
}
|
|
526
1496
|
const pushArgs = [
|
|
@@ -533,11 +1503,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
533
1503
|
const push = gitResult(workspacePath, pushArgs);
|
|
534
1504
|
if (push.exitCode === 0) {
|
|
535
1505
|
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1506
|
+
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
536
1507
|
await input.afterWorkspacePublished?.({
|
|
537
1508
|
publishedHead: localHead,
|
|
538
|
-
activeMountIds:
|
|
1509
|
+
activeMountIds: completedMounts.activeMountIds
|
|
539
1510
|
});
|
|
540
|
-
if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
|
|
541
1511
|
return {
|
|
542
1512
|
outcome: "pushed",
|
|
543
1513
|
startingHead,
|
|
@@ -547,8 +1517,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
547
1517
|
rebaseCount,
|
|
548
1518
|
diffSizeBytes: size,
|
|
549
1519
|
affectedPaths: paths,
|
|
550
|
-
|
|
551
|
-
skippedMountIds: selected.skipped.map(({ id }) => id).sort()
|
|
1520
|
+
...completedMounts
|
|
552
1521
|
};
|
|
553
1522
|
}
|
|
554
1523
|
if (!/(non-fast-forward|fetch first|rejected|failed to push some refs)/i.test(`${push.stderr}
|
|
@@ -570,10 +1539,15 @@ const workspaceGitSyncTestHarness = {
|
|
|
570
1539
|
0 && (module.exports = {
|
|
571
1540
|
MAX_WORKSPACE_GIT_DIFF_BYTES,
|
|
572
1541
|
WORKSPACE_GIT_BRANCH,
|
|
1542
|
+
WORKSPACE_GIT_CHECKOUT_DURABILITY,
|
|
573
1543
|
WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
|
|
1544
|
+
WORKSPACE_GIT_HYDRATED_RECEIPT,
|
|
1545
|
+
WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
574
1546
|
ensureWorkspaceGitClone,
|
|
575
1547
|
hydrateWorkspaceGitMounts,
|
|
1548
|
+
recoverWorkspaceGitHydration,
|
|
576
1549
|
resetWorkspaceGit,
|
|
577
1550
|
synchronizeWorkspaceGit,
|
|
1551
|
+
workspaceGitHydrationIsCurrent,
|
|
578
1552
|
workspaceGitSyncTestHarness
|
|
579
1553
|
});
|