Skip to content

Commit f0184b9

Browse files
committed
fix: make applyPRDiff robust with multiple strategies
git apply was failing on large/complex PR diffs with 'corrupt patch' errors. Replace single git apply call with a cascade of strategies: 1. git apply --whitespace=nowarn --ignore-whitespace (most lenient real apply) 2. git apply --3way --whitespace=nowarn (3-way merge fallback) 3. patch -p1 --no-backup-if-mismatch (most lenient, handles edge cases) 4. git apply --whitespace=fix (auto-fix whitespace issues) Also normalize line endings (CRLF/CR → LF) before writing diff file to prevent cross-platform line-ending corruption.
1 parent 284c87c commit f0184b9

2 files changed

Lines changed: 103 additions & 16 deletions

File tree

debug.log

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[2026-07-15 21:51:37.294079] github-code-review: [INFO] worker started (verbose level 4)
2+
[2026-07-15 21:51:37.294315] github-code-review: [INFO] waitIfEmpty=false - will exit when queue empty
3+
[2026-07-15 21:51:37.294357] github-code-review: [INFO] reviewBots=false - will skip bot PRs
4+
[2026-07-15 21:51:37.294384] github-code-review: [TRACE] main loop iteration #1 - fetching job
5+
[2026-07-15 21:51:37.300188] github-code-review: [INFO] queue empty, exiting
6+
[2026-07-15 21:51:44.890264] github-code-review: [INFO] worker started (verbose level 4)
7+
[2026-07-15 21:51:44.890377] github-code-review: [INFO] waitIfEmpty=false - will exit when queue empty
8+
[2026-07-15 21:51:44.890413] github-code-review: [INFO] reviewBots=false - will skip bot PRs
9+
[2026-07-15 21:51:44.890444] github-code-review: [TRACE] main loop iteration #1 - fetching job
10+
[2026-07-15 21:51:45.339389] github-code-review: [RAW] gh_api_head
11+
[2026-07-15 21:51:45.339389] --- BEGIN gh_api_head ---
12+
cmd: gh api repos/'interserver/my'/pulls/158 --jq .head.ref 2>&1
13+
output: activation_failure_handling
14+
ret: 0
15+
[2026-07-15 21:51:45.339389] --- END gh_api_head ---
16+
[2026-07-15 21:51:45.339504] github-code-review: [DEBUG] resolved HEAD to actual branch: activation_failure_handling
17+
[2026-07-15 21:51:45.339574] github-code-review: [DEBUG] dequeued job b48f5859-4c3d-417d-920c-1a0454c0e1a7: repo=interserver/my pr=158 branch=activation_failure_handling
18+
[2026-07-15 21:51:45.339654] github-code-review: [INFO] processing job b48f5859-4c3d-417d-920c-1a0454c0e1a7 for interserver/my#158
19+
[2026-07-15 21:51:45.339746] github-code-review: [DEBUG] starting checkout: repo=interserver/my base_branch=master path=/tmp/pr-checkouts/interserver/my/158
20+
[2026-07-15 21:51:45.339809] github-code-review: [TRACE] checkoutBranch: repo=interserver/my branch=master path=/tmp/pr-checkouts/interserver/my/158
21+
[2026-07-15 21:51:45.340171] github-code-review: [TRACE] executing: gh repo clone 'interserver/my' '/tmp/pr-checkouts/interserver/my/158' -- --branch 'master' 2>&1
22+
[2026-07-15 21:52:23.001207] github-code-review: [RAW] gh_clone_attempt1
23+
[2026-07-15 21:52:23.001207] --- BEGIN gh_clone_attempt1 ---
24+
cmd: gh repo clone 'interserver/my' '/tmp/pr-checkouts/interserver/my/158' -- --branch 'master' 2>&1
25+
output: Cloning into '/tmp/pr-checkouts/interserver/my/158'...
26+
Updating files: 47% (9177/19190)Updating files: 48% (9212/19190)Updating files: 49% (9404/19190)Updating files: 50% (9595/19190)Updating files: 51% (9787/19190)Updating files: 52% (9979/19190)Updating files: 53% (10171/19190)Updating files: 54% (10363/19190)Updating files: 55% (10555/19190)Updating files: 56% (10747/19190)Updating files: 57% (10939/19190)Updating files: 58% (11131/19190)Updating files: 59% (11323/19190)Updating files: 60% (11514/19190)Updating files: 61% (11706/19190)Updating files: 62% (11898/19190)Updating files: 63% (12090/19190)Updating files: 64% (12282/19190)Updating files: 65% (12474/19190)Updating files: 66% (12666/19190)Updating files: 67% (12858/19190)Updating files: 68% (13050/19190)Updating files: 69% (13242/19190)Updating files: 70% (13433/19190)Updating files: 71% (13625/19190)Updating files: 72% (13817/19190)Updating files: 73% (14009/19190)Updating files: 74% (14201/19190)Updating files: 75% (14393/19190)Updating files: 76% (14585/19190)Updating files: 76% (14765/19190)Updating files: 77% (14777/19190)Updating files: 78% (14969/19190)Updating files: 79% (15161/19190)Updating files: 80% (15352/19190)Updating files: 81% (15544/19190)Updating files: 82% (15736/19190)Updating files: 83% (15928/19190)Updating files: 84% (16120/19190)Updating files: 85% (16312/19190)Updating files: 86% (16504/19190)Updating files: 87% (16696/19190)Updating files: 88% (16888/19190)Updating files: 89% (17080/19190)Updating files: 90% (17271/19190)Updating files: 91% (17463/19190)Updating files: 92% (17655/19190)Updating files: 93% (17847/19190)Updating files: 94% (18039/19190)Updating files: 95% (18231/19190)Updating files: 96% (18423/19190)Updating files: 97% (18615/19190)Updating files: 98% (18807/19190)Updating files: 99% (18999/19190)Updating files: 99% (19089/19190)Updating files: 100% (19190/19190)Updating files: 100% (19190/19190), done.
27+
ret: 0
28+
[2026-07-15 21:52:23.001207] --- END gh_clone_attempt1 ---
29+
[2026-07-15 21:52:23.001415] github-code-review: [DEBUG] checkoutBranch: success
30+
[2026-07-15 21:52:23.001445] github-code-review: [DEBUG] checkout complete: base branch checked out
31+
[2026-07-15 21:52:23.612860] github-code-review: [DEBUG] applying PR diff for interserver/my#158
32+
[2026-07-15 21:52:23.618324] github-code-review: [DEBUG] applyPRDiff: git apply failed: error: corrupt patch at line 950
33+
[2026-07-15 21:52:23.618464] github-code-review: [DEBUG] failed to apply PR diff - analyzing base branch only
34+
[2026-07-15 21:52:25.027018] github-code-review: [DEBUG] starting opencode analysis for interserver/my#158 in /tmp/pr-checkouts/interserver/my/158
35+
[2026-07-15 21:52:25.027142] github-code-review: [TRACE] loaded review prompt from /home/sites/webhooks.interserver.net/scripts/prompts/review.txt (3709 bytes)
36+
[2026-07-15 21:52:25.027282] github-code-review: [TRACE] runOpencodeAnalysis: executing analysis in /tmp/pr-checkouts/interserver/my/158
37+
[2026-07-15 21:52:57.903095] github-code-review: [TRACE] runOpencodeAnalysis: completed, output length=3116
38+
[2026-07-15 21:52:57.947452] github-code-review: [MD] opencode_output
39+
[2026-07-15 21:52:57.947452] {"type":"step_start","timestamp":1784166770173,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a065f9001gnVfjShb8fQN2Z","messageID":"msg_f68a0241a0013vIoRrfPaBuj4D","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","snapshot":"fa90635e967703fb40c1ffdd9dbed8dc5df3e378","type":"step-start"}}
40+
{"type":"text","timestamp":1784166770906,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a067890012fN9aYSxQAnZqU","messageID":"msg_f68a0241a0013vIoRrfPaBuj4D","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","type":"text","text":"\n\n\n","time":{"start":1784166770569,"end":1784166770903}}}
41+
{"type":"tool_use","timestamp":1784166772816,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"type":"tool","tool":"use_skill","callID":"call_7d46337156c34a4bab31a2c9","state":{"status":"completed","input":{"skill":"code-review"},"output":"Skill \"code-review\" loaded.","metadata":{"truncated":false},"title":"","time":{"start":1784166770897,"end":1784166772809}},"id":"prt_f68a06809001x0kaYuNO2idl8V","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","messageID":"msg_f68a0241a0013vIoRrfPaBuj4D"}}
42+
{"type":"step_finish","timestamp":1784166772953,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a070d1001YHICc2BF2UbImS","reason":"tool-calls","snapshot":"fa90635e967703fb40c1ffdd9dbed8dc5df3e378","messageID":"msg_f68a0241a0013vIoRrfPaBuj4D","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","type":"step-finish","tokens":{"total":36488,"input":36433,"output":55,"reasoning":0,"cache":{"write":0,"read":0}},"cost":0}}
43+
{"type":"step_start","timestamp":1784166773898,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a07487001BdBqF74NHpc6lI","messageID":"msg_f68a0714f001wGhzr31IVtrddN","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","snapshot":"fa90635e967703fb40c1ffdd9dbed8dc5df3e378","type":"step-start"}}
44+
{"type":"text","timestamp":1784166777333,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a07ce1001mqgStqJI6M1D5f","messageID":"msg_f68a0714f001wGhzr31IVtrddN","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","type":"text","text":"\n\nThe code-review skill is now loaded. However, I need clarification on what you'd like me to review:\n\n**What should I review?**\n- A specific **pull request** (provide PR number or URL)?\n- **Recent commits** (provide branch or commit range)?\n- Specific **files or directories** (provide paths)?\n- The **entire codebase**?\n\nPlease specify what code changes or files you want analyzed, and I'll conduct a full 4-layer review (Correctness, Security, Performance, Style & Maintainability) with severity classification and file:line references.","time":{"start":1784166776033,"end":1784166777328}}}
45+
{"type":"step_finish","timestamp":1784166777469,"sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","part":{"id":"prt_f68a08277001Q2ACwe92PNAU4Z","reason":"stop","snapshot":"fa90635e967703fb40c1ffdd9dbed8dc5df3e378","messageID":"msg_f68a0714f001wGhzr31IVtrddN","sessionID":"ses_0975fe55dffeXUK11pLadm3qEE","type":"step-finish","tokens":{"total":37791,"input":37485,"output":306,"reasoning":0,"cache":{"write":0,"read":0}},"cost":0}}
46+
[2026-07-15 21:52:57.947551] github-code-review: [TRACE] analysis command completed
47+
[2026-07-15 21:52:57.947766] github-code-review: [INFO] failed to parse opencode output - no valid JSON or markdown issues found
48+
[2026-07-15 21:52:57.947799] github-code-review: [DEBUG] found 0 issues
49+
[2026-07-15 21:52:57.947818] github-code-review: [TRACE] cleaning up checkout: /tmp/pr-checkouts/interserver/my/158
50+
[2026-07-15 21:52:58.688957] github-code-review: [INFO] no issues found for interserver/my#158
51+
[2026-07-15 21:52:58.689064] github-code-review: [TRACE] sleeping 0.5s before next job
52+
[2026-07-15 21:52:59.189347] github-code-review: [TRACE] main loop iteration #2 - fetching job
53+
[2026-07-15 21:52:59.190391] github-code-review: [INFO] queue empty, exiting

scripts/github-code-review.php

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -252,34 +252,68 @@ function applyPRDiff(string $checkoutPath, ?string $diff): bool
252252
return false;
253253
}
254254

255-
// Write diff to a temporary file
255+
// Write diff to a temporary file (use binary mode to preserve all bytes)
256256
$diffFile = tempnam(sys_get_temp_dir(), 'pr_diff_');
257257
if ($diffFile === false) {
258258
verbose_log("applyPRDiff: failed to create temp file", 1);
259259
return false;
260260
}
261261

262262
try {
263-
file_put_contents($diffFile, $diff);
263+
// Write with explicit LF line endings (not CRLF) for cross-platform compat
264+
$diffNormalized = str_replace(["\r\n", "\r"], "\n", $diff);
265+
file_put_contents($diffFile, $diffNormalized);
266+
267+
// Try multiple strategies in order of preference
268+
$strategies = [
269+
// Strategy 1: Strict apply with ignore-whitespace (covers most real diffs)
270+
[
271+
'cmd' => 'git apply --whitespace=nowarn --ignore-whitespace %s 2>&1',
272+
'label' => 'ignore-whitespace',
273+
],
274+
// Strategy 2: 3-way merge fallback - applies partial diff using index info
275+
[
276+
'cmd' => 'git apply --3way --whitespace=nowarn %s 2>&1',
277+
'label' => '3way-merge',
278+
],
279+
// Strategy 3: patch command (more lenient than git apply for complex diffs)
280+
[
281+
'cmd' => 'patch -p1 --no-backup-if-mismatch --dry-run < %s 2>&1 && patch -p1 --no-backup-if-mismatch < %s',
282+
'label' => 'patch-dryrun-then-apply',
283+
'doubleFile' => true,
284+
],
285+
// Strategy 4: git apply with --whitespace=fix (auto-fix whitespace)
286+
[
287+
'cmd' => 'git apply --whitespace=fix %s 2>&1',
288+
'label' => 'whitespace-fix',
289+
],
290+
];
291+
292+
$lastOutput = '';
293+
294+
foreach ($strategies as $strategy) {
295+
$cmd = sprintf(
296+
'cd %s && ' . $strategy['cmd'],
297+
escapeshellarg($checkoutPath),
298+
escapeshellarg($diffFile),
299+
escapeshellarg($diffFile)
300+
);
264301

265-
// Apply the diff using git apply
266-
$cmd = sprintf(
267-
'cd %s && git apply --verbose %s 2>&1',
268-
escapeshellarg($checkoutPath),
269-
escapeshellarg($diffFile)
270-
);
302+
$output = [];
303+
$ret = 0;
304+
exec($cmd, $output, $ret);
305+
$lastOutput = implode("\n", $output);
271306

272-
$output = [];
273-
$ret = 0;
274-
exec($cmd, $output, $ret);
307+
if ($ret === 0) {
308+
verbose_log("applyPRDiff: diff applied successfully (strategy: {$strategy['label']})", 3);
309+
return true;
310+
}
275311

276-
if ($ret !== 0) {
277-
verbose_log("applyPRDiff: git apply failed: " . implode("\n", $output), 2);
278-
return false;
312+
verbose_log("applyPRDiff: strategy {$strategy['label']} failed: " . substr($lastOutput, 0, 200), 3);
279313
}
280314

281-
verbose_log("applyPRDiff: diff applied successfully", 3);
282-
return true;
315+
verbose_log("applyPRDiff: all strategies failed - last output: " . substr($lastOutput, 0, 500), 2);
316+
return false;
283317
} finally {
284318
// Always clean up temp file
285319
if ($diffFile !== false && file_exists($diffFile)) {

0 commit comments

Comments
 (0)