patchrelay 0.41.6 → 0.41.7
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/dist/build-info.json +3 -3
- package/dist/worktree-manager.js +52 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/worktree-manager.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ensureDir, execCommand } from "./utils.js";
|
|
4
|
+
const FETCH_RETRY_DELAYS_MS = [0, 1_000];
|
|
5
|
+
const TRANSIENT_FETCH_ERROR_PATTERNS = [
|
|
6
|
+
/connection reset by peer/i,
|
|
7
|
+
/recv failure/i,
|
|
8
|
+
/tls handshake timeout/i,
|
|
9
|
+
/connection timed out/i,
|
|
10
|
+
/operation timed out/i,
|
|
11
|
+
/timed out after \d+ms/i,
|
|
12
|
+
/remote end hung up unexpectedly/i,
|
|
13
|
+
/unexpected disconnect while reading sideband packet/i,
|
|
14
|
+
];
|
|
4
15
|
export class WorktreeManager {
|
|
5
16
|
config;
|
|
6
17
|
constructor(config) {
|
|
@@ -84,9 +95,7 @@ export class WorktreeManager {
|
|
|
84
95
|
// Fetch latest main so the branch forks from a clean, up-to-date base.
|
|
85
96
|
// This prevents branch contamination when local HEAD has drifted.
|
|
86
97
|
// freshenWorktree in run-orchestrator acts as a secondary safety net.
|
|
87
|
-
const fetchResult = await
|
|
88
|
-
timeoutMs: 60_000,
|
|
89
|
-
});
|
|
98
|
+
const fetchResult = await this.fetchWithTransientRetry(repoPath, "main");
|
|
90
99
|
if (fetchResult.exitCode !== 0) {
|
|
91
100
|
throw new Error(`Failed to fetch origin/main before creating issue worktree: ${fetchResult.stderr?.slice(0, 300) ?? "git fetch failed"}`);
|
|
92
101
|
}
|
|
@@ -95,6 +104,34 @@ export class WorktreeManager {
|
|
|
95
104
|
throw new Error(`Failed to create issue worktree at ${worktreePath}: ${addResult.stderr?.slice(0, 300) ?? "git worktree add failed"}`);
|
|
96
105
|
}
|
|
97
106
|
}
|
|
107
|
+
async fetchWithTransientRetry(repoPath, branchName) {
|
|
108
|
+
let lastResult;
|
|
109
|
+
let lastError;
|
|
110
|
+
for (let attempt = 0; attempt <= FETCH_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
111
|
+
if (attempt > 0) {
|
|
112
|
+
await delay(FETCH_RETRY_DELAYS_MS[attempt - 1] ?? 0);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const result = await execCommand(this.config.runner.gitBin, ["-C", repoPath, "fetch", "origin", branchName], {
|
|
116
|
+
timeoutMs: 60_000,
|
|
117
|
+
});
|
|
118
|
+
if (result.exitCode === 0 || !isTransientFetchFailure(result.stderr)) {
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
lastResult = result;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (!isTransientFetchFailure(error instanceof Error ? error.message : String(error))) {
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (lastResult) {
|
|
131
|
+
return lastResult;
|
|
132
|
+
}
|
|
133
|
+
throw lastError ?? new Error(`Failed to fetch origin/${branchName}`);
|
|
134
|
+
}
|
|
98
135
|
async assertTrustedExistingWorktree(repoPath, worktreeRoot, worktreePath, options) {
|
|
99
136
|
const worktreeStats = lstatSync(worktreePath);
|
|
100
137
|
if (worktreeStats.isSymbolicLink()) {
|
|
@@ -145,3 +182,15 @@ function isPathWithinRoot(rootPath, candidatePath) {
|
|
|
145
182
|
const relative = path.relative(rootPath, candidatePath);
|
|
146
183
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
147
184
|
}
|
|
185
|
+
function isTransientFetchFailure(message) {
|
|
186
|
+
if (!message) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return TRANSIENT_FETCH_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
190
|
+
}
|
|
191
|
+
function delay(delayMs) {
|
|
192
|
+
if (delayMs <= 0) {
|
|
193
|
+
return Promise.resolve();
|
|
194
|
+
}
|
|
195
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
196
|
+
}
|