@ricsam/r5d-worker 0.0.75 → 0.0.77

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.
@@ -28,6 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var project_worktrees_exports = {};
30
30
  __export(project_worktrees_exports, {
31
+ PROJECT_WORKTREE_SNAPSHOT_PREFIX: () => PROJECT_WORKTREE_SNAPSHOT_PREFIX,
32
+ cleanupStaleProjectWorktreeSnapshots: () => cleanupStaleProjectWorktreeSnapshots,
31
33
  createLinkedProjectBranch: () => createLinkedProjectBranch,
32
34
  deleteLinkedProjectBranch: () => deleteLinkedProjectBranch,
33
35
  deleteProjectMirrorBranch: () => deleteProjectMirrorBranch,
@@ -45,8 +47,13 @@ var import_node_crypto = require("node:crypto");
45
47
  var import_node_fs = __toESM(require("node:fs"), 1);
46
48
  var import_node_os = __toESM(require("node:os"), 1);
47
49
  var import_node_path = __toESM(require("node:path"), 1);
50
+ var import_git_process_environment = require("./git-process-environment.cjs");
48
51
  var import_managed_paths = require("./managed-paths.cjs");
49
52
  var import_working_tree_mirror = require("./working-tree-mirror.cjs");
53
+ const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
54
+ const PROJECT_WORKTREE_SNAPSHOT_NAME = new RegExp(
55
+ `^${PROJECT_WORKTREE_SNAPSHOT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([1-9]\\d*)-([A-Za-z0-9]{6})$`
56
+ );
50
57
  function projectWorktreeConfigurationFingerprint(input) {
51
58
  return (0, import_node_crypto.createHash)("sha256").update(
52
59
  JSON.stringify({
@@ -72,25 +79,15 @@ const NON_RECURSIVE_GIT_CONFIG = [
72
79
  "-c",
73
80
  "push.recurseSubmodules=false"
74
81
  ];
75
- function normalizedHttpOrigin(value) {
76
- try {
77
- const url = new URL(value);
78
- return `${url.protocol}//${url.host}`;
79
- } catch {
80
- return value.replace(/\/+$/, "");
81
- }
82
- }
83
- function authArgs(auth) {
84
- if (!auth) return [];
85
- const key = `http.${normalizedHttpOrigin(auth.extraHeaderUrl)}/.extraHeader`;
86
- return ["-c", "http.extraHeader=", "-c", `${key}=`, "-c", `${key}=${auth.header}`];
82
+ function gitCommandArgs(args) {
83
+ return ["git", ...NON_RECURSIVE_GIT_CONFIG, ...args];
87
84
  }
88
- function gitResult(cwd, args, auth) {
89
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args], {
85
+ function gitResult(cwd, args) {
86
+ const result = Bun.spawnSync(gitCommandArgs(args), {
90
87
  cwd,
91
88
  stdout: "pipe",
92
89
  stderr: "pipe",
93
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
90
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
94
91
  });
95
92
  return {
96
93
  exitCode: result.exitCode,
@@ -98,13 +95,13 @@ function gitResult(cwd, args, auth) {
98
95
  stderr: result.stderr.toString().trim()
99
96
  };
100
97
  }
101
- function git(cwd, args, action, auth) {
102
- const result = gitResult(cwd, args, auth);
98
+ function git(cwd, args, action) {
99
+ const result = gitResult(cwd, args);
103
100
  if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
104
101
  return result.stdout;
105
102
  }
106
- function tryGit(cwd, args, auth) {
107
- return gitResult(cwd, args, auth).exitCode === 0;
103
+ function tryGit(cwd, args) {
104
+ return gitResult(cwd, args).exitCode === 0;
108
105
  }
109
106
  function branchPath(projectRoot, branchName) {
110
107
  (0, import_managed_paths.validateManagedBranchName)(branchName);
@@ -167,17 +164,63 @@ function hasCompatibleLinkedProjectWorktreeLayout(input) {
167
164
  return kind === null || kind === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
168
165
  });
169
166
  }
167
+ function createProjectWorktreeSnapshotRoot() {
168
+ return import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), `${PROJECT_WORKTREE_SNAPSHOT_PREFIX}${process.pid}-`));
169
+ }
170
170
  function snapshotBranchTrees(projectRoot, branches) {
171
- const root = import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), "r5d-project-worktrees-"));
171
+ const root = createProjectWorktreeSnapshotRoot();
172
172
  const paths = /* @__PURE__ */ new Map();
173
- for (const { branchName } of branches) {
174
- const checkoutPath = branchPath(projectRoot, branchName);
175
- if (!import_node_fs.default.existsSync(checkoutPath)) continue;
176
- const snapshotPath = import_node_path.default.join(root, ...branchName.split("/"));
177
- (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
178
- paths.set(branchName, snapshotPath);
173
+ try {
174
+ for (const { branchName } of branches) {
175
+ const checkoutPath = branchPath(projectRoot, branchName);
176
+ if (!import_node_fs.default.existsSync(checkoutPath)) continue;
177
+ const snapshotPath = import_node_path.default.join(root, ...branchName.split("/"));
178
+ (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
179
+ paths.set(branchName, snapshotPath);
180
+ }
181
+ return { root, paths };
182
+ } catch (error) {
183
+ import_node_fs.default.rmSync(root, { recursive: true, force: true });
184
+ throw error;
185
+ }
186
+ }
187
+ function isProcessAlive(processId) {
188
+ try {
189
+ process.kill(processId, 0);
190
+ return true;
191
+ } catch (error) {
192
+ return error.code !== "ESRCH";
179
193
  }
180
- return { root, paths };
194
+ }
195
+ function cleanupStaleProjectWorktreeSnapshots(input = {}) {
196
+ const temporaryRoot = import_node_path.default.resolve(input.temporaryRoot ?? import_node_os.default.tmpdir());
197
+ const processAlive = input.processAlive ?? isProcessAlive;
198
+ const removed = [];
199
+ const failed = [];
200
+ let entries;
201
+ try {
202
+ entries = import_node_fs.default.readdirSync(temporaryRoot, { withFileTypes: true });
203
+ } catch (error) {
204
+ if (error.code === "ENOENT") return { removed, failed };
205
+ return { removed, failed: [{ path: temporaryRoot, error: error instanceof Error ? error.message : String(error) }] };
206
+ }
207
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
208
+ const match = PROJECT_WORKTREE_SNAPSHOT_NAME.exec(entry.name);
209
+ if (!match) continue;
210
+ const ownerProcessId = Number(match[1]);
211
+ if (!Number.isSafeInteger(ownerProcessId) || processAlive(ownerProcessId)) continue;
212
+ const candidate = import_node_path.default.join(temporaryRoot, entry.name);
213
+ try {
214
+ const stat = import_node_fs.default.lstatSync(candidate);
215
+ if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
216
+ import_node_fs.default.rmSync(candidate, { recursive: true, force: true });
217
+ removed.push(candidate);
218
+ } catch (error) {
219
+ if (error.code === "ENOENT") continue;
220
+ failed.push({ path: candidate, error: error instanceof Error ? error.message : String(error) });
221
+ }
222
+ }
223
+ return { removed, failed };
181
224
  }
182
225
  function configureRepository(input) {
183
226
  if (tryGit(input.primaryPath, ["remote", "get-url", "origin"])) {
@@ -185,7 +228,16 @@ function configureRepository(input) {
185
228
  } else {
186
229
  git(input.primaryPath, ["remote", "add", "origin", input.originUrl], "configure project origin");
187
230
  }
231
+ tryGit(input.primaryPath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
188
232
  git(input.primaryPath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset project credential helpers");
233
+ git(input.primaryPath, ["config", "--local", "credential.useHttpPath", "false"], "configure project credential path matching");
234
+ if (input.originCredentialUsername) {
235
+ git(
236
+ input.primaryPath,
237
+ ["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.originUrl), input.originCredentialUsername],
238
+ "configure project credential username"
239
+ );
240
+ }
189
241
  if (input.credentialHelper) {
190
242
  git(
191
243
  input.primaryPath,
@@ -202,19 +254,25 @@ function configureRepository(input) {
202
254
  }
203
255
  }
204
256
  function fetchProjectHeads(input) {
205
- const originFetch = gitResult(
206
- input.primaryPath,
207
- ["fetch", "--no-recurse-submodules", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"],
208
- input.originAuth
209
- );
257
+ const originFetch = gitResult(input.primaryPath, [
258
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.originUrl, input.credentialHelper, input.originCredentialUsername),
259
+ "fetch",
260
+ "--no-recurse-submodules",
261
+ "--prune",
262
+ "origin",
263
+ "+refs/heads/*:refs/remotes/origin/*"
264
+ ]);
210
265
  if (originFetch.exitCode !== 0) {
211
266
  throw new Error(`Fetch project origin: ${originFetch.stderr || originFetch.stdout || `git exited ${originFetch.exitCode}`}`);
212
267
  }
213
- const mirrorFetch = gitResult(
214
- input.primaryPath,
215
- ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
216
- input.mirrorAuth
217
- );
268
+ const mirrorFetch = gitResult(input.primaryPath, [
269
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.mirrorCredentialUsername),
270
+ "fetch",
271
+ "--no-recurse-submodules",
272
+ "--prune",
273
+ input.mirrorUrl,
274
+ "+refs/heads/*:refs/r5d/mirror/*"
275
+ ]);
218
276
  if (mirrorFetch.exitCode !== 0 && !/(couldn't find remote ref|does not have any commits|remote repository is empty|no such ref)/i.test(
219
277
  `${mirrorFetch.stderr}
220
278
  ${mirrorFetch.stdout}`
@@ -224,11 +282,19 @@ ${mirrorFetch.stdout}`
224
282
  }
225
283
  function ensureCommitAvailable(input) {
226
284
  if (tryGit(input.primaryPath, ["cat-file", "-e", `${input.commitHash}^{commit}`])) return;
227
- for (const [url, auth] of [
228
- [input.mirrorUrl, input.mirrorAuth],
229
- [input.originUrl, input.originAuth]
285
+ for (const [url, username] of [
286
+ [input.mirrorUrl, input.mirrorCredentialUsername],
287
+ [input.originUrl, input.originCredentialUsername]
230
288
  ]) {
231
- if (tryGit(input.primaryPath, ["fetch", "--no-recurse-submodules", "--no-tags", url, input.commitHash], auth)) return;
289
+ if (tryGit(input.primaryPath, [
290
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(url, input.credentialHelper, username),
291
+ "fetch",
292
+ "--no-recurse-submodules",
293
+ "--no-tags",
294
+ url,
295
+ input.commitHash
296
+ ]))
297
+ return;
232
298
  }
233
299
  throw new Error(`Project base commit ${input.commitHash} is unavailable from origin and the canonical mirror`);
234
300
  }
@@ -373,7 +439,7 @@ function createLinkedProjectBranch(input) {
373
439
  });
374
440
  if (overlappingWorktree) throw new Error(`Project branch folder overlaps linked worktree ${overlappingWorktree}`);
375
441
  const sourceHead = git(sourcePath, ["rev-parse", "HEAD"], "resolve source branch head");
376
- const snapshotRoot = import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), "r5d-project-branch-"));
442
+ const snapshotRoot = createProjectWorktreeSnapshotRoot();
377
443
  try {
378
444
  (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: sourcePath, targetRoot: snapshotRoot, sourceMode: "git", deletionMode: "all" });
379
445
  git(sourcePath, ["branch", input.branchName, sourceHead], `create project branch ${input.branchName}`);
@@ -432,11 +498,13 @@ function removeProjectWorktrees(input) {
432
498
  }
433
499
  function deleteProjectMirrorBranch(input) {
434
500
  (0, import_managed_paths.validateManagedBranchName)(input.branchName);
435
- const deleted = gitResult(
436
- input.gitDirectory,
437
- ["push", "--no-recurse-submodules", input.mirrorUrl, `:refs/heads/${input.branchName}`],
438
- input.mirrorAuth
439
- );
501
+ const deleted = gitResult(input.gitDirectory, [
502
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
503
+ "push",
504
+ "--no-recurse-submodules",
505
+ input.mirrorUrl,
506
+ `:refs/heads/${input.branchName}`
507
+ ]);
440
508
  if (deleted.exitCode !== 0 && !/(remote ref does not exist|unable to delete|no such ref)/i.test(`${deleted.stderr}
441
509
  ${deleted.stdout}`)) {
442
510
  throw new Error(
@@ -456,9 +524,14 @@ function pushProjectMirrorHeads(input) {
456
524
  }
457
525
  git(
458
526
  checkoutPath,
459
- ["push", "--no-recurse-submodules", input.mirrorUrl, `+HEAD:refs/heads/${branchName}`],
460
- `push hidden project mirror for ${branchName}`,
461
- input.mirrorAuth
527
+ [
528
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
529
+ "push",
530
+ "--no-recurse-submodules",
531
+ input.mirrorUrl,
532
+ `+HEAD:refs/heads/${branchName}`
533
+ ],
534
+ `push hidden project mirror for ${branchName}`
462
535
  );
463
536
  results.push({ branchName, head, pushed: true });
464
537
  }
@@ -466,11 +539,14 @@ function pushProjectMirrorHeads(input) {
466
539
  }
467
540
  function fastForwardProjectHeadsFromMirror(input) {
468
541
  const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
469
- const fetch = gitResult(
470
- primaryPath,
471
- ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
472
- input.mirrorAuth
473
- );
542
+ const fetch = gitResult(primaryPath, [
543
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
544
+ "fetch",
545
+ "--no-recurse-submodules",
546
+ "--prune",
547
+ input.mirrorUrl,
548
+ "+refs/heads/*:refs/r5d/mirror/*"
549
+ ]);
474
550
  if (fetch.exitCode !== 0)
475
551
  throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
476
552
  return [...input.branchNames].sort().map((branchName) => {
@@ -487,10 +563,13 @@ function fastForwardProjectHeadsFromMirror(input) {
487
563
  });
488
564
  }
489
565
  const projectWorktreesTestHarness = {
490
- commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args]
566
+ commandArgs: gitCommandArgs,
567
+ configureRepository
491
568
  };
492
569
  // Annotate the CommonJS export names for ESM import in node:
493
570
  0 && (module.exports = {
571
+ PROJECT_WORKTREE_SNAPSHOT_PREFIX,
572
+ cleanupStaleProjectWorktreeSnapshots,
494
573
  createLinkedProjectBranch,
495
574
  deleteLinkedProjectBranch,
496
575
  deleteProjectMirrorBranch,
@@ -34,6 +34,7 @@ __export(working_tree_mirror_exports, {
34
34
  module.exports = __toCommonJS(working_tree_mirror_exports);
35
35
  var import_node_fs = __toESM(require("node:fs"), 1);
36
36
  var import_node_path = __toESM(require("node:path"), 1);
37
+ var import_git_process_environment = require("./git-process-environment.cjs");
37
38
  function isGitMetadataPath(relativePath) {
38
39
  return relativePath.split("/").includes(".git");
39
40
  }
@@ -70,7 +71,7 @@ function gitEligibleRoots(root) {
70
71
  cwd: root,
71
72
  stdout: "pipe",
72
73
  stderr: "pipe",
73
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
74
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
74
75
  }
75
76
  );
76
77
  if (result.exitCode !== 0) {
@@ -40,6 +40,7 @@ __export(workspace_git_sync_exports, {
40
40
  module.exports = __toCommonJS(workspace_git_sync_exports);
41
41
  var import_node_fs = __toESM(require("node:fs"), 1);
42
42
  var import_node_path = __toESM(require("node:path"), 1);
43
+ var import_git_process_environment = require("./git-process-environment.cjs");
43
44
  var import_working_tree_mirror = require("./working-tree-mirror.cjs");
44
45
  const WORKSPACE_GIT_BRANCH = "main";
45
46
  const MAX_WORKSPACE_GIT_DIFF_BYTES = 5 * 1024 * 1024;
@@ -53,25 +54,18 @@ const NON_RECURSIVE_GIT_CONFIG = [
53
54
  "-c",
54
55
  "push.recurseSubmodules=false"
55
56
  ];
56
- function normalizedHttpOrigin(value) {
57
- try {
58
- const url = new URL(value);
59
- return `${url.protocol}//${url.host}`;
60
- } catch {
61
- return value.replace(/\/+$/, "");
62
- }
57
+ function gitCommandArgs(args) {
58
+ return ["git", ...NON_RECURSIVE_GIT_CONFIG, ...args];
63
59
  }
64
- function authArgs(auth) {
65
- if (!auth) return [];
66
- const key = `http.${normalizedHttpOrigin(auth.extraHeaderUrl)}/.extraHeader`;
67
- return ["-c", "http.extraHeader=", "-c", `${key}=`, "-c", `${key}=${auth.header}`];
60
+ function workspaceCloneCommandArgs(args, remoteUrl, credentialHelper, credentialUsername) {
61
+ return gitCommandArgs([...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername), ...args]);
68
62
  }
69
- function gitResult(cwd, args, auth) {
70
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args], {
63
+ function gitResult(cwd, args) {
64
+ const result = Bun.spawnSync(gitCommandArgs(args), {
71
65
  cwd,
72
66
  stdout: "pipe",
73
67
  stderr: "pipe",
74
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
68
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
75
69
  });
76
70
  return {
77
71
  exitCode: result.exitCode,
@@ -79,13 +73,13 @@ function gitResult(cwd, args, auth) {
79
73
  stderr: result.stderr.toString().trim()
80
74
  };
81
75
  }
82
- function git(cwd, args, action, auth) {
83
- const result = gitResult(cwd, args, auth);
76
+ function git(cwd, args, action) {
77
+ const result = gitResult(cwd, args);
84
78
  if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
85
79
  return result.stdout;
86
80
  }
87
- function tryGit(cwd, args, auth) {
88
- return gitResult(cwd, args, auth).exitCode === 0;
81
+ function tryGit(cwd, args) {
82
+ return gitResult(cwd, args).exitCode === 0;
89
83
  }
90
84
  function revParse(workspacePath, revision) {
91
85
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
@@ -131,9 +125,22 @@ function configureWorkspaceRepository(input) {
131
125
  } else {
132
126
  git(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
133
127
  }
128
+ tryGit(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
134
129
  git(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
130
+ git(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
131
+ if (input.credentialUsername) {
132
+ git(
133
+ input.workspacePath,
134
+ ["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.remoteUrl), input.credentialUsername],
135
+ "configure workspace credential username"
136
+ );
137
+ }
135
138
  if (input.credentialHelper) {
136
- git(input.workspacePath, ["config", "--local", "--add", "credential.helper", input.credentialHelper], "configure workspace credential helper");
139
+ git(
140
+ input.workspacePath,
141
+ ["config", "--local", "--add", "credential.helper", input.credentialHelper],
142
+ "configure workspace credential helper"
143
+ );
137
144
  }
138
145
  const name = input.gitIdentity.name.trim();
139
146
  const email = input.gitIdentity.email.trim();
@@ -141,19 +148,16 @@ function configureWorkspaceRepository(input) {
141
148
  git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
142
149
  git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
143
150
  }
144
- function fetchWorkspaceHead(workspacePath, remoteAuth) {
145
- const result = gitResult(
146
- workspacePath,
147
- [
148
- "fetch",
149
- "--no-recurse-submodules",
150
- "--prune",
151
- "--update-shallow",
152
- "origin",
153
- `+refs/heads/${WORKSPACE_GIT_BRANCH}:refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`
154
- ],
155
- remoteAuth
156
- );
151
+ function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
152
+ const result = gitResult(workspacePath, [
153
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
154
+ "fetch",
155
+ "--no-recurse-submodules",
156
+ "--prune",
157
+ "--update-shallow",
158
+ "origin",
159
+ `+refs/heads/${WORKSPACE_GIT_BRANCH}:refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`
160
+ ]);
157
161
  if (result.exitCode !== 0) {
158
162
  const detail = `${result.stderr}
159
163
  ${result.stdout}`;
@@ -170,11 +174,17 @@ function ensureWorkspaceGitClone(input) {
170
174
  if (!import_node_fs.default.existsSync(gitPath)) {
171
175
  import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
172
176
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(workspacePath), { recursive: true });
173
- const clone = gitResult(
174
- void 0,
177
+ const cloneCommand = workspaceCloneCommandArgs(
175
178
  ["clone", "--no-recurse-submodules", "--branch", WORKSPACE_GIT_BRANCH, input.remoteUrl, workspacePath],
176
- input.remoteAuth
179
+ input.remoteUrl,
180
+ input.credentialHelper,
181
+ input.credentialUsername
177
182
  );
183
+ const clone = Bun.spawnSync(cloneCommand, {
184
+ stdout: "pipe",
185
+ stderr: "pipe",
186
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
187
+ });
178
188
  if (clone.exitCode !== 0) {
179
189
  import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
180
190
  import_node_fs.default.mkdirSync(workspacePath, { recursive: true });
@@ -187,7 +197,7 @@ function ensureWorkspaceGitClone(input) {
187
197
  if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
188
198
  updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
189
199
  }
190
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
200
+ const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
191
201
  let localHead = revParse(workspacePath, "HEAD");
192
202
  if (!localHead && remoteHead) {
193
203
  git(
@@ -273,7 +283,7 @@ function resetWorkspaceGit(input) {
273
283
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
274
284
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
275
285
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
276
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
286
+ const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
277
287
  if (remoteHead) {
278
288
  git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
279
289
  git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
@@ -299,9 +309,10 @@ function emptyTreeHash(workspacePath) {
299
309
  stdin: Buffer.alloc(0),
300
310
  stdout: "pipe",
301
311
  stderr: "pipe",
302
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
312
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
303
313
  });
304
- if (result.exitCode !== 0) throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
314
+ if (result.exitCode !== 0)
315
+ throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
305
316
  return result.stdout.toString().trim();
306
317
  }
307
318
  function changedPaths(workspacePath, baseRevision, headRevision) {
@@ -310,15 +321,12 @@ function changedPaths(workspacePath, baseRevision, headRevision) {
310
321
  }
311
322
  async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
312
323
  const base = baseRevision ?? emptyTreeHash(workspacePath);
313
- const subprocess = Bun.spawn(
314
- ["git", ...NON_RECURSIVE_GIT_CONFIG, "diff", "--binary", "--no-ext-diff", base, headRevision],
315
- {
316
- cwd: workspacePath,
317
- stdout: "pipe",
318
- stderr: "pipe",
319
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
320
- }
321
- );
324
+ const subprocess = Bun.spawn(["git", ...NON_RECURSIVE_GIT_CONFIG, "diff", "--binary", "--no-ext-diff", base, headRevision], {
325
+ cwd: workspacePath,
326
+ stdout: "pipe",
327
+ stderr: "pipe",
328
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
329
+ });
322
330
  const stderrPromise = new Response(subprocess.stderr).text();
323
331
  const reader = subprocess.stdout.getReader();
324
332
  let total = 0;
@@ -439,7 +447,7 @@ async function synchronizeWorkspaceGit(input) {
439
447
  "commit workspace working trees"
440
448
  );
441
449
  }
442
- let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
450
+ let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
443
451
  let rebaseCount = 0;
444
452
  let updated = false;
445
453
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
@@ -515,10 +523,14 @@ async function synchronizeWorkspaceGit(input) {
515
523
  skippedMountIds: selected.skipped.map(({ id }) => id).sort()
516
524
  };
517
525
  }
518
- const pushArgs = ["push", "--no-recurse-submodules"];
526
+ const pushArgs = [
527
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.remoteUrl, input.credentialHelper, input.credentialUsername),
528
+ "push",
529
+ "--no-recurse-submodules"
530
+ ];
519
531
  if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
520
532
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
521
- const push = gitResult(workspacePath, pushArgs, input.remoteAuth);
533
+ const push = gitResult(workspacePath, pushArgs);
522
534
  if (push.exitCode === 0) {
523
535
  updateIntegratedWorkspaceHead(workspacePath, localHead);
524
536
  await input.afterWorkspacePublished?.({
@@ -543,12 +555,14 @@ async function synchronizeWorkspaceGit(input) {
543
555
  ${push.stdout}`)) {
544
556
  throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
545
557
  }
546
- remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
558
+ remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
547
559
  }
548
560
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
549
561
  }
550
562
  const workspaceGitSyncTestHarness = {
551
- commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args],
563
+ commandArgs: gitCommandArgs,
564
+ workspaceCloneCommandArgs,
565
+ configureWorkspaceRepository,
552
566
  mirrorMountsToWorkspace,
553
567
  hydrateMountsFromWorkspace: hydrateWorkspaceGitMounts
554
568
  };
@@ -0,0 +1,105 @@
1
+ import { devNull } from "node:os";
2
+ const ALLOWED_GIT_ENVIRONMENT_KEYS = [
3
+ "PATH",
4
+ "USER",
5
+ "LOGNAME",
6
+ "SHELL",
7
+ "TMPDIR",
8
+ "TMP",
9
+ "TEMP",
10
+ "TZ",
11
+ "LANG",
12
+ "LANGUAGE",
13
+ "LC_ALL",
14
+ "LC_CTYPE",
15
+ "LC_COLLATE",
16
+ "LC_MESSAGES",
17
+ "LC_MONETARY",
18
+ "LC_NUMERIC",
19
+ "LC_TIME",
20
+ "LC_ADDRESS",
21
+ "LC_IDENTIFICATION",
22
+ "LC_MEASUREMENT",
23
+ "LC_NAME",
24
+ "LC_PAPER",
25
+ "LC_TELEPHONE",
26
+ "SystemRoot",
27
+ "SYSTEMROOT",
28
+ "WINDIR",
29
+ "COMSPEC",
30
+ "PATHEXT",
31
+ "SSL_CERT_FILE",
32
+ "SSL_CERT_DIR",
33
+ "CURL_CA_BUNDLE",
34
+ "GIT_SSL_CAINFO",
35
+ "GIT_SSL_CAPATH",
36
+ "GIT_EXEC_PATH",
37
+ "GIT_OPTIONAL_LOCKS"
38
+ ];
39
+ function workerGitProcessEnvironment(source = process.env) {
40
+ const environment = {
41
+ GIT_TERMINAL_PROMPT: "0",
42
+ GIT_CONFIG_GLOBAL: devNull,
43
+ GIT_CONFIG_NOSYSTEM: "1",
44
+ GIT_ATTR_NOSYSTEM: "1"
45
+ };
46
+ for (const key of ALLOWED_GIT_ENVIRONMENT_KEYS) {
47
+ if (source[key] !== void 0) environment[key] = source[key];
48
+ }
49
+ return environment;
50
+ }
51
+ function gitHttpAuthorizationClearArgs(remoteUrl) {
52
+ let remote;
53
+ try {
54
+ remote = new URL(remoteUrl);
55
+ } catch {
56
+ return ["-c", "http.extraHeader="];
57
+ }
58
+ if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
59
+ throw new Error("Git credentials must not be embedded in a remote URL");
60
+ }
61
+ if (remote.protocol !== "https:" && remote.protocol !== "http:") return ["-c", "http.extraHeader="];
62
+ remote.hash = "";
63
+ remote.search = "";
64
+ return ["-c", "http.extraHeader=", "-c", `http.${remote.origin}/.extraHeader=`, "-c", `http.${remote.href}.extraHeader=`];
65
+ }
66
+ function gitCredentialHelperConfigArgs(credentialHelper) {
67
+ return [
68
+ "-c",
69
+ "credential.helper=",
70
+ ...credentialHelper ? ["-c", `credential.helper=${credentialHelper}`] : [],
71
+ "-c",
72
+ "credential.useHttpPath=false"
73
+ ];
74
+ }
75
+ function gitCredentialUsernameConfigKey(remoteUrl) {
76
+ const remote = new URL(remoteUrl);
77
+ if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
78
+ throw new Error("Git credentials must not be embedded in a remote URL");
79
+ }
80
+ if (remote.protocol !== "https:" && remote.protocol !== "http:") {
81
+ throw new Error("Git credential usernames require an HTTP remote URL");
82
+ }
83
+ remote.hash = "";
84
+ remote.search = "";
85
+ return `credential.${remote.href}.username`;
86
+ }
87
+ function gitCredentialUsernameConfigArgs(remoteUrl, credentialUsername) {
88
+ if (!credentialUsername) return [];
89
+ if (/[\0\r\n]/.test(credentialUsername)) throw new Error("Invalid Git credential username");
90
+ return ["-c", `${gitCredentialUsernameConfigKey(remoteUrl)}=${credentialUsername}`];
91
+ }
92
+ function gitTransportSecurityArgs(remoteUrl, credentialHelper, credentialUsername) {
93
+ return [
94
+ ...gitHttpAuthorizationClearArgs(remoteUrl),
95
+ ...gitCredentialHelperConfigArgs(credentialHelper),
96
+ ...gitCredentialUsernameConfigArgs(remoteUrl, credentialUsername)
97
+ ];
98
+ }
99
+ export {
100
+ gitCredentialHelperConfigArgs,
101
+ gitCredentialUsernameConfigKey,
102
+ gitHttpAuthorizationClearArgs,
103
+ gitTransportSecurityArgs,
104
+ workerGitProcessEnvironment
105
+ };