|
12 | 12 | * |
13 | 13 | * 2. pull_request_target: Runs in BASE repository context (not PR head) |
14 | 14 | * - CRITICAL: For fork PRs, the head branch doesn't exist in base repo |
15 | | - * - Must use `gh pr checkout` to fetch from the fork |
| 15 | + * - Uses refs/pull/N/head to fetch from origin (works for forks too) |
16 | 16 | * - Has write permissions (be cautious with untrusted code) |
17 | 17 | * |
18 | 18 | * 3. Other PR events (issue_comment, pull_request_review, etc.): |
19 | 19 | * - Also run in base repository context |
20 | | - * - Must use `gh pr checkout` to get PR branch |
| 20 | + * - Uses refs/pull/N/head to fetch PR branch |
21 | 21 | * |
22 | 22 | * NOTE: This handler operates within the PR context from the workflow event |
23 | 23 | * and does not support cross-repository operations or target-repo parameters. |
|
26 | 26 | */ |
27 | 27 |
|
28 | 28 | const { getErrorMessage } = require("./error_helpers.cjs"); |
29 | | -const { getGhEnvBypassingIntegrityFilteringForGitOps } = require("./git_helpers.cjs"); |
30 | 29 | const { renderTemplateFromFile } = require("./messages_core.cjs"); |
31 | 30 | const { detectForkPR } = require("./pr_helpers.cjs"); |
32 | 31 | const { ERR_API } = require("./error_codes.cjs"); |
@@ -79,10 +78,10 @@ function logPRContext(eventName, pullRequest) { |
79 | 78 | } |
80 | 79 |
|
81 | 80 | /** |
82 | | - * Fetch full PR commit count from the GitHub API. |
83 | | - * Used when the commit count is not available in the webhook payload. |
| 81 | + * Fetch PR details from the GitHub API. |
| 82 | + * Returns head ref and commit count needed for checkout. |
84 | 83 | */ |
85 | | -async function fetchPRCommitCount(prNumber) { |
| 84 | +async function fetchPRDetails(prNumber) { |
86 | 85 | const { data } = await github.rest.pulls.get({ |
87 | 86 | owner: context.repo.owner, |
88 | 87 | repo: context.repo.repo, |
@@ -115,7 +114,7 @@ async function main() { |
115 | 114 | number: context.payload.issue.number, |
116 | 115 | state: context.payload.issue.state || "open", |
117 | 116 | }; |
118 | | - core.info(`Detected ${eventName} event on PR #${pullRequest.number}, will use gh pr checkout`); |
| 117 | + core.info(`Detected ${eventName} event on PR #${pullRequest.number}, will fetch PR ref`); |
119 | 118 | } |
120 | 119 |
|
121 | 120 | if (!pullRequest) { |
@@ -159,60 +158,36 @@ async function main() { |
159 | 158 | } else { |
160 | 159 | // For pull_request_target, fork pull_request events, and other PR events, |
161 | 160 | // we run in base repository context. |
162 | | - // IMPORTANT: For fork PRs, the head branch doesn't exist in the base repo |
163 | | - // We must use `gh pr checkout` which handles fetching from forks |
| 161 | + // Use refs/pull/N/head which GitHub makes available for all PRs (including forks) |
| 162 | + // so we don't need `gh pr checkout` and avoid GH_HOST / DIFC proxy issues. |
164 | 163 | const prNumber = pullRequest.number; |
165 | 164 |
|
166 | 165 | const strategyReason = |
167 | 166 | eventName === "pull_request_target" |
168 | | - ? "pull_request_target runs in base repo context; for fork PRs, head branch doesn't exist in origin" |
| 167 | + ? "pull_request_target runs in base repo context; fetching via refs/pull/N/head" |
169 | 168 | : eventName === "pull_request" && isFork |
170 | | - ? "pull_request event from fork repository; head branch exists only in fork, not in origin" |
171 | | - : `${eventName} event runs in base repo context; must fetch PR branch`; |
| 169 | + ? "pull_request event from fork repository; fetching via refs/pull/N/head" |
| 170 | + : `${eventName} event runs in base repo context; fetching via refs/pull/N/head`; |
172 | 171 |
|
173 | | - logCheckoutStrategy(eventName, "gh pr checkout", strategyReason); |
| 172 | + logCheckoutStrategy(eventName, "git fetch refs/pull + checkout", strategyReason); |
174 | 173 |
|
175 | 174 | if (isFork) { |
176 | | - core.warning("⚠️ Fork PR detected - gh pr checkout will fetch from fork repository"); |
| 175 | + core.warning("⚠️ Fork PR detected - fetching via refs/pull/N/head from origin"); |
177 | 176 | } |
178 | 177 |
|
179 | | - core.info(`Checking out PR #${prNumber} using gh CLI`); |
| 178 | + // Get PR details from API to determine head ref name and commit count |
| 179 | + const { commitCount, headRef } = await fetchPRDetails(prNumber); |
| 180 | + const fetchDepth = (commitCount || 1) + 1; // +1 to include the merge base |
180 | 181 |
|
181 | | - // Override GH_HOST with the real GitHub hostname so gh pr checkout can resolve |
182 | | - // the repository from git remotes. The DIFC proxy may have set GH_HOST to |
183 | | - // localhost:18443 which doesn't match any remote. |
184 | | - await exec.exec("gh", ["pr", "checkout", prNumber.toString()], { |
185 | | - env: getGhEnvBypassingIntegrityFilteringForGitOps(), |
186 | | - }); |
| 182 | + core.info(`Fetching PR #${prNumber} head via refs/pull/${prNumber}/head (depth: ${fetchDepth} for ${commitCount} PR commit(s))`); |
| 183 | + await exec.exec("git", ["fetch", "origin", `+refs/pull/${prNumber}/head:refs/remotes/origin/pr-head`, `--depth=${fetchDepth}`]); |
187 | 184 |
|
188 | | - // Log the resulting branch after checkout |
189 | | - let currentBranch = ""; |
190 | | - await exec.exec("git", ["branch", "--show-current"], { |
191 | | - listeners: { |
192 | | - stdout: data => { |
193 | | - currentBranch += data.toString(); |
194 | | - }, |
195 | | - }, |
196 | | - }); |
197 | | - currentBranch = currentBranch.trim(); |
198 | | - |
199 | | - // Deepen history to cover all PR commits (gh pr checkout fetches --depth=1 by default) |
200 | | - try { |
201 | | - const { commitCount, headRef } = await fetchPRCommitCount(prNumber); |
202 | | - const fetchDepth = commitCount + 1; // +1 to include the merge base |
203 | | - const branchRef = headRef || currentBranch; |
204 | | - core.info(`Deepening history for PR #${prNumber}: fetching ${fetchDepth} commits (${commitCount} PR commit(s) + merge base)`); |
205 | | - if (branchRef) { |
206 | | - await exec.exec("git", ["fetch", "origin", branchRef, `--depth=${fetchDepth}`]); |
207 | | - } else { |
208 | | - await exec.exec("git", ["fetch", `--depth=${fetchDepth}`]); |
209 | | - } |
210 | | - } catch (depthError) { |
211 | | - core.warning(`Could not deepen PR history: ${getErrorMessage(depthError)}`); |
212 | | - } |
| 185 | + const branchName = headRef || `pr-${prNumber}`; |
| 186 | + core.info(`Checking out branch: ${branchName}`); |
| 187 | + await exec.exec("git", ["checkout", "-B", branchName, "origin/pr-head"]); |
213 | 188 |
|
214 | 189 | core.info(`✅ Successfully checked out PR #${prNumber}`); |
215 | | - core.info(`Current branch: ${currentBranch || "detached HEAD"}`); |
| 190 | + core.info(`Current branch: ${branchName}`); |
216 | 191 | } |
217 | 192 |
|
218 | 193 | // Set output to indicate successful checkout |
|
0 commit comments