Skip to content

Commit f2b0140

Browse files
committed
fix: replace gh pr checkout with git fetch refs/pull to avoid GH_HOST issues
Replace `gh pr checkout` with pure git operations (`git fetch origin +refs/pull/N/head` + `git checkout -B`) for PR checkout in non-fork pull_request_target, issue_comment, and other PR event handlers. GitHub exposes refs/pull/N/head for all PRs including forks, so this works universally. Git operations use remote URLs directly and are unaffected by GH_HOST overrides from the DIFC proxy, eliminating the need for the getGhEnvBypassingIntegrityFilteringForGitOps helper. Also removes the now-unused getGitHubHost() and getGhEnvBypassingIntegrityFilteringForGitOps() from git_helpers.cjs.
1 parent d00a742 commit f2b0140

3 files changed

Lines changed: 87 additions & 222 deletions

File tree

actions/setup/js/checkout_pr_branch.cjs

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
*
1313
* 2. pull_request_target: Runs in BASE repository context (not PR head)
1414
* - 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)
1616
* - Has write permissions (be cautious with untrusted code)
1717
*
1818
* 3. Other PR events (issue_comment, pull_request_review, etc.):
1919
* - 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
2121
*
2222
* NOTE: This handler operates within the PR context from the workflow event
2323
* and does not support cross-repository operations or target-repo parameters.
@@ -26,7 +26,6 @@
2626
*/
2727

2828
const { getErrorMessage } = require("./error_helpers.cjs");
29-
const { getGhEnvBypassingIntegrityFilteringForGitOps } = require("./git_helpers.cjs");
3029
const { renderTemplateFromFile } = require("./messages_core.cjs");
3130
const { detectForkPR } = require("./pr_helpers.cjs");
3231
const { ERR_API } = require("./error_codes.cjs");
@@ -79,10 +78,10 @@ function logPRContext(eventName, pullRequest) {
7978
}
8079

8180
/**
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.
8483
*/
85-
async function fetchPRCommitCount(prNumber) {
84+
async function fetchPRDetails(prNumber) {
8685
const { data } = await github.rest.pulls.get({
8786
owner: context.repo.owner,
8887
repo: context.repo.repo,
@@ -115,7 +114,7 @@ async function main() {
115114
number: context.payload.issue.number,
116115
state: context.payload.issue.state || "open",
117116
};
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`);
119118
}
120119

121120
if (!pullRequest) {
@@ -159,60 +158,36 @@ async function main() {
159158
} else {
160159
// For pull_request_target, fork pull_request events, and other PR events,
161160
// 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.
164163
const prNumber = pullRequest.number;
165164

166165
const strategyReason =
167166
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"
169168
: 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`;
172171

173-
logCheckoutStrategy(eventName, "gh pr checkout", strategyReason);
172+
logCheckoutStrategy(eventName, "git fetch refs/pull + checkout", strategyReason);
174173

175174
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");
177176
}
178177

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
180181

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}`]);
187184

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"]);
213188

214189
core.info(`✅ Successfully checked out PR #${prNumber}`);
215-
core.info(`Current branch: ${currentBranch || "detached HEAD"}`);
190+
core.info(`Current branch: ${branchName}`);
216191
}
217192

218193
// Set output to indicate successful checkout

0 commit comments

Comments
 (0)