Skip to content

Commit 9a46d36

Browse files
authored
Add extensive logging and robust fork detection to PR checkout logic (#14443)
1 parent 9fdee51 commit 9a46d36

6 files changed

Lines changed: 921 additions & 7 deletions

File tree

.changeset/patch-improve-pr-checkout-logging.md

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions/setup/js/checkout_pr_branch.cjs

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,86 @@
33

44
/**
55
* Checkout PR branch when PR context is available
6-
* This script handles both pull_request events and comment events on PRs
6+
*
7+
* This script handles checkout for different GitHub event types:
8+
*
9+
* 1. pull_request: Runs in merge commit context (PR head + base merged)
10+
* - Can use direct git commands since we're already in PR context
11+
* - Branch exists in current checkout
12+
*
13+
* 2. pull_request_target: Runs in BASE repository context (not PR head)
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
16+
* - Has write permissions (be cautious with untrusted code)
17+
*
18+
* 3. Other PR events (issue_comment, pull_request_review, etc.):
19+
* - Also run in base repository context
20+
* - Must use `gh pr checkout` to get PR branch
721
*/
822

923
const { getErrorMessage } = require("./error_helpers.cjs");
1024
const { renderTemplate } = require("./messages_core.cjs");
25+
const { detectForkPR } = require("./pr_helpers.cjs");
1126
const fs = require("fs");
1227

28+
/**
29+
* Log detailed PR context information for debugging
30+
*/
31+
function logPRContext(eventName, pullRequest) {
32+
core.startGroup("📋 PR Context Details");
33+
34+
core.info(`Event type: ${eventName}`);
35+
core.info(`PR number: ${pullRequest.number}`);
36+
core.info(`PR state: ${pullRequest.state || "unknown"}`);
37+
38+
// Log head information
39+
if (pullRequest.head) {
40+
core.info(`Head ref: ${pullRequest.head.ref || "unknown"}`);
41+
core.info(`Head SHA: ${pullRequest.head.sha || "unknown"}`);
42+
43+
if (pullRequest.head.repo) {
44+
core.info(`Head repo: ${pullRequest.head.repo.full_name || "unknown"}`);
45+
core.info(`Head repo owner: ${pullRequest.head.repo.owner?.login || "unknown"}`);
46+
} else {
47+
core.warning("⚠️ Head repo information not available (repo may be deleted)");
48+
}
49+
}
50+
51+
// Log base information
52+
if (pullRequest.base) {
53+
core.info(`Base ref: ${pullRequest.base.ref || "unknown"}`);
54+
core.info(`Base SHA: ${pullRequest.base.sha || "unknown"}`);
55+
56+
if (pullRequest.base.repo) {
57+
core.info(`Base repo: ${pullRequest.base.repo.full_name || "unknown"}`);
58+
core.info(`Base repo owner: ${pullRequest.base.repo.owner?.login || "unknown"}`);
59+
}
60+
}
61+
62+
// Determine if this is a fork PR using the helper function
63+
const { isFork, reason: forkReason } = detectForkPR(pullRequest);
64+
core.info(`Is fork PR: ${isFork} (${forkReason})`);
65+
66+
// Log current repository context
67+
core.info(`Current repository: ${context.repo.owner}/${context.repo.repo}`);
68+
core.info(`GitHub SHA: ${context.sha}`);
69+
70+
core.endGroup();
71+
72+
return { isFork };
73+
}
74+
75+
/**
76+
* Log the checkout strategy being used
77+
*/
78+
function logCheckoutStrategy(eventName, strategy, reason) {
79+
core.startGroup("🔄 Checkout Strategy");
80+
core.info(`Event type: ${eventName}`);
81+
core.info(`Strategy: ${strategy}`);
82+
core.info(`Reason: ${reason}`);
83+
core.endGroup();
84+
}
85+
1386
async function main() {
1487
const eventName = context.eventName;
1588
const pullRequest = context.payload.pull_request;
@@ -24,30 +97,86 @@ async function main() {
2497
core.info(`Pull Request #${pullRequest.number}`);
2598

2699
try {
100+
// Log detailed context for debugging
101+
const { isFork } = logPRContext(eventName, pullRequest);
102+
27103
if (eventName === "pull_request") {
28-
// For pull_request events, use the head ref directly
104+
// For pull_request events, we run in the merge commit context
105+
// The PR branch is already available, so we can use direct git commands
29106
const branchName = pullRequest.head.ref;
30-
core.info(`Checking out PR branch: ${branchName}`);
31107

108+
logCheckoutStrategy(eventName, "git fetch + checkout", "pull_request event runs in merge commit context with PR branch available");
109+
110+
core.info(`Fetching branch: ${branchName} from origin`);
32111
await exec.exec("git", ["fetch", "origin", branchName]);
112+
113+
core.info(`Checking out branch: ${branchName}`);
33114
await exec.exec("git", ["checkout", branchName]);
34115

35116
core.info(`✅ Successfully checked out branch: ${branchName}`);
36117
} else {
37-
// For comment events on PRs, use gh pr checkout with PR number
118+
// For pull_request_target and other PR events, we run in base repository context
119+
// IMPORTANT: For fork PRs, the head branch doesn't exist in the base repo
120+
// We must use `gh pr checkout` which handles fetching from forks
38121
const prNumber = pullRequest.number;
39-
core.info(`Checking out PR #${prNumber} using gh pr checkout`);
40122

123+
const strategyReason = eventName === "pull_request_target" ? "pull_request_target runs in base repo context; for fork PRs, head branch doesn't exist in origin" : `${eventName} event runs in base repo context; must fetch PR branch`;
124+
125+
logCheckoutStrategy(eventName, "gh pr checkout", strategyReason);
126+
127+
if (isFork) {
128+
core.warning("⚠️ Fork PR detected - gh pr checkout will fetch from fork repository");
129+
}
130+
131+
core.info(`Checking out PR #${prNumber} using gh CLI`);
41132
await exec.exec("gh", ["pr", "checkout", prNumber.toString()]);
42133

134+
// Log the resulting branch after checkout
135+
let currentBranch = "";
136+
await exec.exec("git", ["branch", "--show-current"], {
137+
listeners: {
138+
stdout: data => {
139+
currentBranch += data.toString();
140+
},
141+
},
142+
});
143+
currentBranch = currentBranch.trim();
144+
43145
core.info(`✅ Successfully checked out PR #${prNumber}`);
146+
core.info(`Current branch: ${currentBranch || "detached HEAD"}`);
44147
}
45148

46149
// Set output to indicate successful checkout
47150
core.setOutput("checkout_pr_success", "true");
48151
} catch (error) {
49152
const errorMsg = getErrorMessage(error);
50153

154+
// Log detailed error context
155+
core.startGroup("❌ Checkout Error Details");
156+
core.error(`Event type: ${eventName}`);
157+
core.error(`PR number: ${pullRequest.number}`);
158+
core.error(`Error message: ${errorMsg}`);
159+
160+
if (pullRequest.head?.ref) {
161+
core.error(`Attempted to check out: ${pullRequest.head.ref}`);
162+
}
163+
164+
// Log current git state for debugging
165+
try {
166+
core.info("Current git status:");
167+
await exec.exec("git", ["status"]);
168+
169+
core.info("Available remotes:");
170+
await exec.exec("git", ["remote", "-v"]);
171+
172+
core.info("Current branch:");
173+
await exec.exec("git", ["branch", "--show-current"]);
174+
} catch (gitError) {
175+
core.warning(`Could not retrieve git state: ${getErrorMessage(gitError)}`);
176+
}
177+
178+
core.endGroup();
179+
51180
// Set output to indicate checkout failure
52181
core.setOutput("checkout_pr_success", "false");
53182

0 commit comments

Comments
 (0)