@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.
@@ -2,8 +2,13 @@ import { createHash } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { gitCredentialUsernameConfigKey, gitTransportSecurityArgs, workerGitProcessEnvironment } from "./git-process-environment.mjs";
5
6
  import { validateManagedBranchName } from "./managed-paths.mjs";
6
7
  import { mirrorWorkingTree } from "./working-tree-mirror.mjs";
8
+ const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
9
+ const PROJECT_WORKTREE_SNAPSHOT_NAME = new RegExp(
10
+ `^${PROJECT_WORKTREE_SNAPSHOT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([1-9]\\d*)-([A-Za-z0-9]{6})$`
11
+ );
7
12
  function projectWorktreeConfigurationFingerprint(input) {
8
13
  return createHash("sha256").update(
9
14
  JSON.stringify({
@@ -29,25 +34,15 @@ const NON_RECURSIVE_GIT_CONFIG = [
29
34
  "-c",
30
35
  "push.recurseSubmodules=false"
31
36
  ];
32
- function normalizedHttpOrigin(value) {
33
- try {
34
- const url = new URL(value);
35
- return `${url.protocol}//${url.host}`;
36
- } catch {
37
- return value.replace(/\/+$/, "");
38
- }
39
- }
40
- function authArgs(auth) {
41
- if (!auth) return [];
42
- const key = `http.${normalizedHttpOrigin(auth.extraHeaderUrl)}/.extraHeader`;
43
- return ["-c", "http.extraHeader=", "-c", `${key}=`, "-c", `${key}=${auth.header}`];
37
+ function gitCommandArgs(args) {
38
+ return ["git", ...NON_RECURSIVE_GIT_CONFIG, ...args];
44
39
  }
45
- function gitResult(cwd, args, auth) {
46
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args], {
40
+ function gitResult(cwd, args) {
41
+ const result = Bun.spawnSync(gitCommandArgs(args), {
47
42
  cwd,
48
43
  stdout: "pipe",
49
44
  stderr: "pipe",
50
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
45
+ env: workerGitProcessEnvironment()
51
46
  });
52
47
  return {
53
48
  exitCode: result.exitCode,
@@ -55,13 +50,13 @@ function gitResult(cwd, args, auth) {
55
50
  stderr: result.stderr.toString().trim()
56
51
  };
57
52
  }
58
- function git(cwd, args, action, auth) {
59
- const result = gitResult(cwd, args, auth);
53
+ function git(cwd, args, action) {
54
+ const result = gitResult(cwd, args);
60
55
  if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
61
56
  return result.stdout;
62
57
  }
63
- function tryGit(cwd, args, auth) {
64
- return gitResult(cwd, args, auth).exitCode === 0;
58
+ function tryGit(cwd, args) {
59
+ return gitResult(cwd, args).exitCode === 0;
65
60
  }
66
61
  function branchPath(projectRoot, branchName) {
67
62
  validateManagedBranchName(branchName);
@@ -124,17 +119,63 @@ function hasCompatibleLinkedProjectWorktreeLayout(input) {
124
119
  return kind === null || kind === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
125
120
  });
126
121
  }
122
+ function createProjectWorktreeSnapshotRoot() {
123
+ return fs.mkdtempSync(path.join(os.tmpdir(), `${PROJECT_WORKTREE_SNAPSHOT_PREFIX}${process.pid}-`));
124
+ }
127
125
  function snapshotBranchTrees(projectRoot, branches) {
128
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "r5d-project-worktrees-"));
126
+ const root = createProjectWorktreeSnapshotRoot();
129
127
  const paths = /* @__PURE__ */ new Map();
130
- for (const { branchName } of branches) {
131
- const checkoutPath = branchPath(projectRoot, branchName);
132
- if (!fs.existsSync(checkoutPath)) continue;
133
- const snapshotPath = path.join(root, ...branchName.split("/"));
134
- mirrorWorkingTree({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
135
- paths.set(branchName, snapshotPath);
128
+ try {
129
+ for (const { branchName } of branches) {
130
+ const checkoutPath = branchPath(projectRoot, branchName);
131
+ if (!fs.existsSync(checkoutPath)) continue;
132
+ const snapshotPath = path.join(root, ...branchName.split("/"));
133
+ mirrorWorkingTree({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
134
+ paths.set(branchName, snapshotPath);
135
+ }
136
+ return { root, paths };
137
+ } catch (error) {
138
+ fs.rmSync(root, { recursive: true, force: true });
139
+ throw error;
140
+ }
141
+ }
142
+ function isProcessAlive(processId) {
143
+ try {
144
+ process.kill(processId, 0);
145
+ return true;
146
+ } catch (error) {
147
+ return error.code !== "ESRCH";
136
148
  }
137
- return { root, paths };
149
+ }
150
+ function cleanupStaleProjectWorktreeSnapshots(input = {}) {
151
+ const temporaryRoot = path.resolve(input.temporaryRoot ?? os.tmpdir());
152
+ const processAlive = input.processAlive ?? isProcessAlive;
153
+ const removed = [];
154
+ const failed = [];
155
+ let entries;
156
+ try {
157
+ entries = fs.readdirSync(temporaryRoot, { withFileTypes: true });
158
+ } catch (error) {
159
+ if (error.code === "ENOENT") return { removed, failed };
160
+ return { removed, failed: [{ path: temporaryRoot, error: error instanceof Error ? error.message : String(error) }] };
161
+ }
162
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
163
+ const match = PROJECT_WORKTREE_SNAPSHOT_NAME.exec(entry.name);
164
+ if (!match) continue;
165
+ const ownerProcessId = Number(match[1]);
166
+ if (!Number.isSafeInteger(ownerProcessId) || processAlive(ownerProcessId)) continue;
167
+ const candidate = path.join(temporaryRoot, entry.name);
168
+ try {
169
+ const stat = fs.lstatSync(candidate);
170
+ if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
171
+ fs.rmSync(candidate, { recursive: true, force: true });
172
+ removed.push(candidate);
173
+ } catch (error) {
174
+ if (error.code === "ENOENT") continue;
175
+ failed.push({ path: candidate, error: error instanceof Error ? error.message : String(error) });
176
+ }
177
+ }
178
+ return { removed, failed };
138
179
  }
139
180
  function configureRepository(input) {
140
181
  if (tryGit(input.primaryPath, ["remote", "get-url", "origin"])) {
@@ -142,7 +183,16 @@ function configureRepository(input) {
142
183
  } else {
143
184
  git(input.primaryPath, ["remote", "add", "origin", input.originUrl], "configure project origin");
144
185
  }
186
+ tryGit(input.primaryPath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
145
187
  git(input.primaryPath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset project credential helpers");
188
+ git(input.primaryPath, ["config", "--local", "credential.useHttpPath", "false"], "configure project credential path matching");
189
+ if (input.originCredentialUsername) {
190
+ git(
191
+ input.primaryPath,
192
+ ["config", "--local", "--replace-all", gitCredentialUsernameConfigKey(input.originUrl), input.originCredentialUsername],
193
+ "configure project credential username"
194
+ );
195
+ }
146
196
  if (input.credentialHelper) {
147
197
  git(
148
198
  input.primaryPath,
@@ -159,19 +209,25 @@ function configureRepository(input) {
159
209
  }
160
210
  }
161
211
  function fetchProjectHeads(input) {
162
- const originFetch = gitResult(
163
- input.primaryPath,
164
- ["fetch", "--no-recurse-submodules", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"],
165
- input.originAuth
166
- );
212
+ const originFetch = gitResult(input.primaryPath, [
213
+ ...gitTransportSecurityArgs(input.originUrl, input.credentialHelper, input.originCredentialUsername),
214
+ "fetch",
215
+ "--no-recurse-submodules",
216
+ "--prune",
217
+ "origin",
218
+ "+refs/heads/*:refs/remotes/origin/*"
219
+ ]);
167
220
  if (originFetch.exitCode !== 0) {
168
221
  throw new Error(`Fetch project origin: ${originFetch.stderr || originFetch.stdout || `git exited ${originFetch.exitCode}`}`);
169
222
  }
170
- const mirrorFetch = gitResult(
171
- input.primaryPath,
172
- ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
173
- input.mirrorAuth
174
- );
223
+ const mirrorFetch = gitResult(input.primaryPath, [
224
+ ...gitTransportSecurityArgs(input.mirrorUrl, input.credentialHelper, input.mirrorCredentialUsername),
225
+ "fetch",
226
+ "--no-recurse-submodules",
227
+ "--prune",
228
+ input.mirrorUrl,
229
+ "+refs/heads/*:refs/r5d/mirror/*"
230
+ ]);
175
231
  if (mirrorFetch.exitCode !== 0 && !/(couldn't find remote ref|does not have any commits|remote repository is empty|no such ref)/i.test(
176
232
  `${mirrorFetch.stderr}
177
233
  ${mirrorFetch.stdout}`
@@ -181,11 +237,19 @@ ${mirrorFetch.stdout}`
181
237
  }
182
238
  function ensureCommitAvailable(input) {
183
239
  if (tryGit(input.primaryPath, ["cat-file", "-e", `${input.commitHash}^{commit}`])) return;
184
- for (const [url, auth] of [
185
- [input.mirrorUrl, input.mirrorAuth],
186
- [input.originUrl, input.originAuth]
240
+ for (const [url, username] of [
241
+ [input.mirrorUrl, input.mirrorCredentialUsername],
242
+ [input.originUrl, input.originCredentialUsername]
187
243
  ]) {
188
- if (tryGit(input.primaryPath, ["fetch", "--no-recurse-submodules", "--no-tags", url, input.commitHash], auth)) return;
244
+ if (tryGit(input.primaryPath, [
245
+ ...gitTransportSecurityArgs(url, input.credentialHelper, username),
246
+ "fetch",
247
+ "--no-recurse-submodules",
248
+ "--no-tags",
249
+ url,
250
+ input.commitHash
251
+ ]))
252
+ return;
189
253
  }
190
254
  throw new Error(`Project base commit ${input.commitHash} is unavailable from origin and the canonical mirror`);
191
255
  }
@@ -330,7 +394,7 @@ function createLinkedProjectBranch(input) {
330
394
  });
331
395
  if (overlappingWorktree) throw new Error(`Project branch folder overlaps linked worktree ${overlappingWorktree}`);
332
396
  const sourceHead = git(sourcePath, ["rev-parse", "HEAD"], "resolve source branch head");
333
- const snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), "r5d-project-branch-"));
397
+ const snapshotRoot = createProjectWorktreeSnapshotRoot();
334
398
  try {
335
399
  mirrorWorkingTree({ sourceRoot: sourcePath, targetRoot: snapshotRoot, sourceMode: "git", deletionMode: "all" });
336
400
  git(sourcePath, ["branch", input.branchName, sourceHead], `create project branch ${input.branchName}`);
@@ -389,11 +453,13 @@ function removeProjectWorktrees(input) {
389
453
  }
390
454
  function deleteProjectMirrorBranch(input) {
391
455
  validateManagedBranchName(input.branchName);
392
- const deleted = gitResult(
393
- input.gitDirectory,
394
- ["push", "--no-recurse-submodules", input.mirrorUrl, `:refs/heads/${input.branchName}`],
395
- input.mirrorAuth
396
- );
456
+ const deleted = gitResult(input.gitDirectory, [
457
+ ...gitTransportSecurityArgs(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
458
+ "push",
459
+ "--no-recurse-submodules",
460
+ input.mirrorUrl,
461
+ `:refs/heads/${input.branchName}`
462
+ ]);
397
463
  if (deleted.exitCode !== 0 && !/(remote ref does not exist|unable to delete|no such ref)/i.test(`${deleted.stderr}
398
464
  ${deleted.stdout}`)) {
399
465
  throw new Error(
@@ -413,9 +479,14 @@ function pushProjectMirrorHeads(input) {
413
479
  }
414
480
  git(
415
481
  checkoutPath,
416
- ["push", "--no-recurse-submodules", input.mirrorUrl, `+HEAD:refs/heads/${branchName}`],
417
- `push hidden project mirror for ${branchName}`,
418
- input.mirrorAuth
482
+ [
483
+ ...gitTransportSecurityArgs(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
484
+ "push",
485
+ "--no-recurse-submodules",
486
+ input.mirrorUrl,
487
+ `+HEAD:refs/heads/${branchName}`
488
+ ],
489
+ `push hidden project mirror for ${branchName}`
419
490
  );
420
491
  results.push({ branchName, head, pushed: true });
421
492
  }
@@ -423,11 +494,14 @@ function pushProjectMirrorHeads(input) {
423
494
  }
424
495
  function fastForwardProjectHeadsFromMirror(input) {
425
496
  const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
426
- const fetch = gitResult(
427
- primaryPath,
428
- ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
429
- input.mirrorAuth
430
- );
497
+ const fetch = gitResult(primaryPath, [
498
+ ...gitTransportSecurityArgs(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
499
+ "fetch",
500
+ "--no-recurse-submodules",
501
+ "--prune",
502
+ input.mirrorUrl,
503
+ "+refs/heads/*:refs/r5d/mirror/*"
504
+ ]);
431
505
  if (fetch.exitCode !== 0)
432
506
  throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
433
507
  return [...input.branchNames].sort().map((branchName) => {
@@ -444,9 +518,12 @@ function fastForwardProjectHeadsFromMirror(input) {
444
518
  });
445
519
  }
446
520
  const projectWorktreesTestHarness = {
447
- commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args]
521
+ commandArgs: gitCommandArgs,
522
+ configureRepository
448
523
  };
449
524
  export {
525
+ PROJECT_WORKTREE_SNAPSHOT_PREFIX,
526
+ cleanupStaleProjectWorktreeSnapshots,
450
527
  createLinkedProjectBranch,
451
528
  deleteLinkedProjectBranch,
452
529
  deleteProjectMirrorBranch,
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { workerGitProcessEnvironment } from "./git-process-environment.mjs";
3
4
  function isGitMetadataPath(relativePath) {
4
5
  return relativePath.split("/").includes(".git");
5
6
  }
@@ -36,7 +37,7 @@ function gitEligibleRoots(root) {
36
37
  cwd: root,
37
38
  stdout: "pipe",
38
39
  stderr: "pipe",
39
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
40
+ env: workerGitProcessEnvironment()
40
41
  }
41
42
  );
42
43
  if (result.exitCode !== 0) {
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { gitCredentialUsernameConfigKey, gitTransportSecurityArgs, workerGitProcessEnvironment } from "./git-process-environment.mjs";
3
4
  import { mirrorWorkingTree } from "./working-tree-mirror.mjs";
4
5
  const WORKSPACE_GIT_BRANCH = "main";
5
6
  const MAX_WORKSPACE_GIT_DIFF_BYTES = 5 * 1024 * 1024;
@@ -13,25 +14,18 @@ const NON_RECURSIVE_GIT_CONFIG = [
13
14
  "-c",
14
15
  "push.recurseSubmodules=false"
15
16
  ];
16
- function normalizedHttpOrigin(value) {
17
- try {
18
- const url = new URL(value);
19
- return `${url.protocol}//${url.host}`;
20
- } catch {
21
- return value.replace(/\/+$/, "");
22
- }
17
+ function gitCommandArgs(args) {
18
+ return ["git", ...NON_RECURSIVE_GIT_CONFIG, ...args];
23
19
  }
24
- function authArgs(auth) {
25
- if (!auth) return [];
26
- const key = `http.${normalizedHttpOrigin(auth.extraHeaderUrl)}/.extraHeader`;
27
- return ["-c", "http.extraHeader=", "-c", `${key}=`, "-c", `${key}=${auth.header}`];
20
+ function workspaceCloneCommandArgs(args, remoteUrl, credentialHelper, credentialUsername) {
21
+ return gitCommandArgs([...gitTransportSecurityArgs(remoteUrl, credentialHelper, credentialUsername), ...args]);
28
22
  }
29
- function gitResult(cwd, args, auth) {
30
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args], {
23
+ function gitResult(cwd, args) {
24
+ const result = Bun.spawnSync(gitCommandArgs(args), {
31
25
  cwd,
32
26
  stdout: "pipe",
33
27
  stderr: "pipe",
34
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
28
+ env: workerGitProcessEnvironment()
35
29
  });
36
30
  return {
37
31
  exitCode: result.exitCode,
@@ -39,13 +33,13 @@ function gitResult(cwd, args, auth) {
39
33
  stderr: result.stderr.toString().trim()
40
34
  };
41
35
  }
42
- function git(cwd, args, action, auth) {
43
- const result = gitResult(cwd, args, auth);
36
+ function git(cwd, args, action) {
37
+ const result = gitResult(cwd, args);
44
38
  if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
45
39
  return result.stdout;
46
40
  }
47
- function tryGit(cwd, args, auth) {
48
- return gitResult(cwd, args, auth).exitCode === 0;
41
+ function tryGit(cwd, args) {
42
+ return gitResult(cwd, args).exitCode === 0;
49
43
  }
50
44
  function revParse(workspacePath, revision) {
51
45
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
@@ -91,9 +85,22 @@ function configureWorkspaceRepository(input) {
91
85
  } else {
92
86
  git(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
93
87
  }
88
+ tryGit(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
94
89
  git(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
90
+ git(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
91
+ if (input.credentialUsername) {
92
+ git(
93
+ input.workspacePath,
94
+ ["config", "--local", "--replace-all", gitCredentialUsernameConfigKey(input.remoteUrl), input.credentialUsername],
95
+ "configure workspace credential username"
96
+ );
97
+ }
95
98
  if (input.credentialHelper) {
96
- git(input.workspacePath, ["config", "--local", "--add", "credential.helper", input.credentialHelper], "configure workspace credential helper");
99
+ git(
100
+ input.workspacePath,
101
+ ["config", "--local", "--add", "credential.helper", input.credentialHelper],
102
+ "configure workspace credential helper"
103
+ );
97
104
  }
98
105
  const name = input.gitIdentity.name.trim();
99
106
  const email = input.gitIdentity.email.trim();
@@ -101,19 +108,16 @@ function configureWorkspaceRepository(input) {
101
108
  git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
102
109
  git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
103
110
  }
104
- function fetchWorkspaceHead(workspacePath, remoteAuth) {
105
- const result = gitResult(
106
- workspacePath,
107
- [
108
- "fetch",
109
- "--no-recurse-submodules",
110
- "--prune",
111
- "--update-shallow",
112
- "origin",
113
- `+refs/heads/${WORKSPACE_GIT_BRANCH}:refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`
114
- ],
115
- remoteAuth
116
- );
111
+ function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
112
+ const result = gitResult(workspacePath, [
113
+ ...gitTransportSecurityArgs(remoteUrl, credentialHelper, credentialUsername),
114
+ "fetch",
115
+ "--no-recurse-submodules",
116
+ "--prune",
117
+ "--update-shallow",
118
+ "origin",
119
+ `+refs/heads/${WORKSPACE_GIT_BRANCH}:refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`
120
+ ]);
117
121
  if (result.exitCode !== 0) {
118
122
  const detail = `${result.stderr}
119
123
  ${result.stdout}`;
@@ -130,11 +134,17 @@ function ensureWorkspaceGitClone(input) {
130
134
  if (!fs.existsSync(gitPath)) {
131
135
  fs.rmSync(workspacePath, { recursive: true, force: true });
132
136
  fs.mkdirSync(path.dirname(workspacePath), { recursive: true });
133
- const clone = gitResult(
134
- void 0,
137
+ const cloneCommand = workspaceCloneCommandArgs(
135
138
  ["clone", "--no-recurse-submodules", "--branch", WORKSPACE_GIT_BRANCH, input.remoteUrl, workspacePath],
136
- input.remoteAuth
139
+ input.remoteUrl,
140
+ input.credentialHelper,
141
+ input.credentialUsername
137
142
  );
143
+ const clone = Bun.spawnSync(cloneCommand, {
144
+ stdout: "pipe",
145
+ stderr: "pipe",
146
+ env: workerGitProcessEnvironment()
147
+ });
138
148
  if (clone.exitCode !== 0) {
139
149
  fs.rmSync(workspacePath, { recursive: true, force: true });
140
150
  fs.mkdirSync(workspacePath, { recursive: true });
@@ -147,7 +157,7 @@ function ensureWorkspaceGitClone(input) {
147
157
  if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
148
158
  updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
149
159
  }
150
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
160
+ const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
151
161
  let localHead = revParse(workspacePath, "HEAD");
152
162
  if (!localHead && remoteHead) {
153
163
  git(
@@ -233,7 +243,7 @@ function resetWorkspaceGit(input) {
233
243
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
234
244
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
235
245
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
236
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
246
+ const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
237
247
  if (remoteHead) {
238
248
  git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
239
249
  git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
@@ -259,9 +269,10 @@ function emptyTreeHash(workspacePath) {
259
269
  stdin: Buffer.alloc(0),
260
270
  stdout: "pipe",
261
271
  stderr: "pipe",
262
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
272
+ env: workerGitProcessEnvironment()
263
273
  });
264
- if (result.exitCode !== 0) throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
274
+ if (result.exitCode !== 0)
275
+ throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
265
276
  return result.stdout.toString().trim();
266
277
  }
267
278
  function changedPaths(workspacePath, baseRevision, headRevision) {
@@ -270,15 +281,12 @@ function changedPaths(workspacePath, baseRevision, headRevision) {
270
281
  }
271
282
  async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
272
283
  const base = baseRevision ?? emptyTreeHash(workspacePath);
273
- const subprocess = Bun.spawn(
274
- ["git", ...NON_RECURSIVE_GIT_CONFIG, "diff", "--binary", "--no-ext-diff", base, headRevision],
275
- {
276
- cwd: workspacePath,
277
- stdout: "pipe",
278
- stderr: "pipe",
279
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
280
- }
281
- );
284
+ const subprocess = Bun.spawn(["git", ...NON_RECURSIVE_GIT_CONFIG, "diff", "--binary", "--no-ext-diff", base, headRevision], {
285
+ cwd: workspacePath,
286
+ stdout: "pipe",
287
+ stderr: "pipe",
288
+ env: workerGitProcessEnvironment()
289
+ });
282
290
  const stderrPromise = new Response(subprocess.stderr).text();
283
291
  const reader = subprocess.stdout.getReader();
284
292
  let total = 0;
@@ -399,7 +407,7 @@ async function synchronizeWorkspaceGit(input) {
399
407
  "commit workspace working trees"
400
408
  );
401
409
  }
402
- let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
410
+ let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
403
411
  let rebaseCount = 0;
404
412
  let updated = false;
405
413
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
@@ -475,10 +483,14 @@ async function synchronizeWorkspaceGit(input) {
475
483
  skippedMountIds: selected.skipped.map(({ id }) => id).sort()
476
484
  };
477
485
  }
478
- const pushArgs = ["push", "--no-recurse-submodules"];
486
+ const pushArgs = [
487
+ ...gitTransportSecurityArgs(input.remoteUrl, input.credentialHelper, input.credentialUsername),
488
+ "push",
489
+ "--no-recurse-submodules"
490
+ ];
479
491
  if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
480
492
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
481
- const push = gitResult(workspacePath, pushArgs, input.remoteAuth);
493
+ const push = gitResult(workspacePath, pushArgs);
482
494
  if (push.exitCode === 0) {
483
495
  updateIntegratedWorkspaceHead(workspacePath, localHead);
484
496
  await input.afterWorkspacePublished?.({
@@ -503,12 +515,14 @@ async function synchronizeWorkspaceGit(input) {
503
515
  ${push.stdout}`)) {
504
516
  throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
505
517
  }
506
- remoteHead = fetchWorkspaceHead(workspacePath, input.remoteAuth);
518
+ remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
507
519
  }
508
520
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
509
521
  }
510
522
  const workspaceGitSyncTestHarness = {
511
- commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args],
523
+ commandArgs: gitCommandArgs,
524
+ workspaceCloneCommandArgs,
525
+ configureWorkspaceRepository,
512
526
  mirrorMountsToWorkspace,
513
527
  hydrateMountsFromWorkspace: hydrateWorkspaceGitMounts
514
528
  };
@@ -0,0 +1,9 @@
1
+ /** Build the complete environment for a worker-owned Git child process. */
2
+ export declare function workerGitProcessEnvironment(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
3
+ /** Clear inherited and repository-local HTTP authorization headers for one transport URL. */
4
+ export declare function gitHttpAuthorizationClearArgs(remoteUrl: string): string[];
5
+ /** Select only the app-owned credential store, regardless of repository-local helpers. */
6
+ export declare function gitCredentialHelperConfigArgs(credentialHelper?: string | null): string[];
7
+ export declare function gitCredentialUsernameConfigKey(remoteUrl: string): string;
8
+ /** Non-secret command configuration required for an authenticated HTTP transport. */
9
+ export declare function gitTransportSecurityArgs(remoteUrl: string, credentialHelper?: string | null, credentialUsername?: string | null): string[];
@@ -130,6 +130,30 @@ type ResolvedWorkerFilePath = {
130
130
  virtualRootPath: string;
131
131
  };
132
132
  export declare function resolveWorkerFilePath(branchPath: string, inputPath: string, builtInPaths?: WorkerBuiltInToolPaths): ResolvedWorkerFilePath;
133
+ declare function readOnlyCredentialStoreHelper(storePath: string): string;
134
+ declare function legacySharedCredentialStorePath(): string;
135
+ declare function credentialStorePathForProject(projectId: string): string;
136
+ declare function credentialStorePathForWorkspace(remoteUrl: string): string;
137
+ declare function removeLegacySharedCredentialStore(storePath?: string): void;
138
+ declare function resetCredentialStoreDirectory(directoryPath?: string): void;
139
+ declare function pruneCredentialStoreFiles(requiredStorePaths: ReadonlySet<string>, directoryPath?: string): string[];
140
+ declare function replaceCredentialStoreFile(storePath: string, content: string): void;
141
+ declare function credentialHelperForRemotes(remotes: readonly {
142
+ remoteUrl: string;
143
+ authHeader: string | null | undefined;
144
+ }[], storePath: string): string | null;
145
+ export declare const workerGitSecurityTestHarness: {
146
+ commandArgs: (args: string[]) => string[];
147
+ credentialHelperForRemotes: typeof credentialHelperForRemotes;
148
+ credentialStorePathForProject: typeof credentialStorePathForProject;
149
+ credentialStorePathForWorkspace: typeof credentialStorePathForWorkspace;
150
+ legacySharedCredentialStorePath: typeof legacySharedCredentialStorePath;
151
+ removeLegacySharedCredentialStore: typeof removeLegacySharedCredentialStore;
152
+ pruneCredentialStoreFiles: typeof pruneCredentialStoreFiles;
153
+ readOnlyCredentialStoreHelper: typeof readOnlyCredentialStoreHelper;
154
+ resetCredentialStoreDirectory: typeof resetCredentialStoreDirectory;
155
+ replaceCredentialStoreFile: typeof replaceCredentialStoreFile;
156
+ };
133
157
  export declare function githubCliEnv(token: string | null | undefined): Record<string, string>;
134
158
  type ResolvedWorkerSessionTarget = {
135
159
  target: WorkerSessionTarget;
@@ -1,7 +1,4 @@
1
- export type GitHttpAuth = {
2
- extraHeaderUrl: string;
3
- header: string;
4
- };
1
+ export declare const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
5
2
  export type ProjectWorktreeBranch = {
6
3
  branchName: string;
7
4
  baseCommitHash: string;
@@ -27,20 +24,41 @@ export declare function projectWorktreeConfigurationFingerprint(input: {
27
24
  email: string;
28
25
  } | null;
29
26
  }): string;
27
+ declare function gitCommandArgs(args: string[]): string[];
30
28
  export declare function hasLinkedProjectWorktreeLayout(input: {
31
29
  projectRoot: string;
32
30
  primaryBranchName: string;
33
31
  branches: readonly Pick<ProjectWorktreeBranch, "branchName">[];
34
32
  }): boolean;
33
+ export declare function cleanupStaleProjectWorktreeSnapshots(input?: {
34
+ temporaryRoot?: string;
35
+ processAlive?: (processId: number) => boolean;
36
+ }): {
37
+ removed: string[];
38
+ failed: Array<{
39
+ path: string;
40
+ error: string;
41
+ }>;
42
+ };
43
+ declare function configureRepository(input: {
44
+ primaryPath: string;
45
+ originUrl: string;
46
+ credentialHelper?: string | null;
47
+ originCredentialUsername?: string | null;
48
+ gitIdentity?: {
49
+ name: string;
50
+ email: string;
51
+ } | null;
52
+ }): void;
35
53
  export declare function ensureProjectWorktrees(input: {
36
54
  projectRoot: string;
37
55
  primaryBranchName: string;
38
56
  branches: readonly ProjectWorktreeBranch[];
39
57
  originUrl: string;
40
- originAuth?: GitHttpAuth;
41
58
  mirrorUrl: string;
42
- mirrorAuth?: GitHttpAuth;
43
59
  credentialHelper?: string | null;
60
+ originCredentialUsername?: string | null;
61
+ mirrorCredentialUsername?: string | null;
44
62
  gitIdentity?: {
45
63
  name: string;
46
64
  email: string;
@@ -70,13 +88,15 @@ export declare function deleteProjectMirrorBranch(input: {
70
88
  gitDirectory: string;
71
89
  branchName: string;
72
90
  mirrorUrl: string;
73
- mirrorAuth?: GitHttpAuth;
91
+ credentialHelper?: string | null;
92
+ credentialUsername?: string | null;
74
93
  }): void;
75
94
  export declare function pushProjectMirrorHeads(input: {
76
95
  projectRoot: string;
77
96
  branchNames: readonly string[];
78
97
  mirrorUrl: string;
79
- mirrorAuth?: GitHttpAuth;
98
+ credentialHelper?: string | null;
99
+ credentialUsername?: string | null;
80
100
  onlyBranches?: ReadonlySet<string>;
81
101
  }): Array<{
82
102
  branchName: string;
@@ -89,7 +109,8 @@ export declare function fastForwardProjectHeadsFromMirror(input: {
89
109
  primaryBranchName: string;
90
110
  branchNames: readonly string[];
91
111
  mirrorUrl: string;
92
- mirrorAuth?: GitHttpAuth;
112
+ credentialHelper?: string | null;
113
+ credentialUsername?: string | null;
93
114
  }): Array<{
94
115
  branchName: string;
95
116
  previousHead: string;
@@ -97,5 +118,7 @@ export declare function fastForwardProjectHeadsFromMirror(input: {
97
118
  fastForwarded: boolean;
98
119
  }>;
99
120
  export declare const projectWorktreesTestHarness: {
100
- commandArgs: (args: string[], auth?: GitHttpAuth) => string[];
121
+ commandArgs: typeof gitCommandArgs;
122
+ configureRepository: typeof configureRepository;
101
123
  };
124
+ export {};