Compare commits

..

1 Commits

Author SHA1 Message Date
Colin McDonnell efb4ad186f Improve remote tracking 2025-12-15 23:56:47 -08:00
5 changed files with 68 additions and 68 deletions
+26 -30
View File
@@ -83327,7 +83327,7 @@ function query({
// package.json // package.json
var package_default = { var package_default = {
name: "@pullfrog/action", name: "@pullfrog/action",
version: "0.0.142", version: "0.0.143",
type: "module", type: "module",
files: [ files: [
"index.js", "index.js",
@@ -123148,19 +123148,19 @@ function PushBranchTool(ctx) {
parameters: PushBranch, parameters: PushBranch,
execute: execute(ctx, async ({ branchName, force }) => { execute: execute(ctx, async ({ branchName, force }) => {
const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }); const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
log.info(`Pushing branch ${branch} to ${remote}`); const isUrl = remote.startsWith("https://");
const args3 = force ? ["push", "--force", ...isUrl ? [] : ["-u"], remote, branch] : ["push", ...isUrl ? [] : ["-u"], remote, branch];
log.info(`Pushing branch ${branch} to ${isUrl ? "(fork URL)" : remote}`);
if (force) { if (force) {
log.warning(`Force pushing - this will overwrite remote history`); log.warning(`Force pushing - this will overwrite remote history`);
$("git", ["push", "--force", "-u", remote, branch]);
} else {
$("git", ["push", "-u", remote, branch]);
} }
$("git", args3);
return { return {
success: true, success: true,
branch, branch,
remote, remote: isUrl ? "(fork URL)" : remote,
force, force,
message: `Successfully pushed branch ${branch} to ${remote}` message: `Successfully pushed branch ${branch}`
}; };
}) })
}); });
@@ -123859,7 +123859,7 @@ function setupGitConfig() {
); );
} }
} }
function setupGit(ctx) { async function setupGit(ctx) {
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info("\u{1F527} Setting up git authentication..."); log.info("\u{1F527} Setting up git authentication...");
try { try {
@@ -123885,29 +123885,25 @@ function setupGit(ctx) {
env: { GH_TOKEN: ctx.githubInstallationToken } env: { GH_TOKEN: ctx.githubInstallationToken }
}); });
log.info(`\u2713 Successfully checked out PR #${prNumber}`); log.info(`\u2713 Successfully checked out PR #${prNumber}`);
const pushRemote = detectPushRemote(); const pr = await ctx.octokit.rest.pulls.get({
if (pushRemote !== "origin") { owner: ctx.owner,
log.info(`\u{1F374} Fork PR detected, will push to remote: ${pushRemote}`); repo: ctx.name,
const forkUrl = $("git", ["remote", "get-url", pushRemote], { cwd: repoDir, log: false }); pull_number: prNumber
const authedForkUrl = forkUrl.replace( });
"https://github.com/", const headRepo = pr.data.head.repo;
`https://x-access-token:${ctx.githubInstallationToken}@github.com/` const baseRepo = pr.data.base.repo;
if (!headRepo || headRepo.full_name === baseRepo.full_name) {
return { pushRemote: "origin" };
}
if (!pr.data.maintainer_can_modify) {
log.warning(
`\u26A0\uFE0F Fork PR from ${headRepo.owner.login} does not allow maintainer edits. Push may fail.`
); );
$("git", ["remote", "set-url", pushRemote, authedForkUrl], { cwd: repoDir });
log.info(`\u2713 Updated ${pushRemote} URL with authentication token`);
}
return { pushRemote };
}
function detectPushRemote() {
try {
const branch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
const upstream = $("git", ["rev-parse", "--abbrev-ref", `${branch}@{upstream}`], {
log: false
});
return upstream.split("/")[0];
} catch {
return "origin";
} }
const token = process.env.GITHUB_TOKEN || ctx.githubInstallationToken;
const forkUrl = `https://x-access-token:${token}@github.com/${headRepo.full_name}.git`;
log.info(`\u{1F374} Fork PR detected, will push to: ${headRepo.full_name}`);
return { pushRemote: forkUrl };
} }
// utils/timer.ts // utils/timer.ts
@@ -123946,7 +123942,7 @@ async function main(inputs) {
const partialCtx = await initializeContext(inputs, payload); const partialCtx = await initializeContext(inputs, payload);
const ctx = partialCtx; const ctx = partialCtx;
timer.checkpoint("initializeContext"); timer.checkpoint("initializeContext");
const { pushRemote } = setupGit(ctx); const { pushRemote } = await setupGit(ctx);
ctx.pushRemote = pushRemote; ctx.pushRemote = pushRemote;
timer.checkpoint("setupGit"); timer.checkpoint("setupGit");
await setupTempDirectory(ctx); await setupTempDirectory(ctx);
+1 -1
View File
@@ -64,7 +64,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
const ctx = partialCtx as Context; const ctx = partialCtx as Context;
timer.checkpoint("initializeContext"); timer.checkpoint("initializeContext");
const { pushRemote } = setupGit(ctx); const { pushRemote } = await setupGit(ctx);
ctx.pushRemote = pushRemote; ctx.pushRemote = pushRemote;
timer.checkpoint("setupGit"); timer.checkpoint("setupGit");
+10 -6
View File
@@ -152,20 +152,24 @@ export function PushBranchTool(ctx: Context) {
execute: execute(ctx, async ({ branchName, force }) => { execute: execute(ctx, async ({ branchName, force }) => {
const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }); const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
log.info(`Pushing branch ${branch} to ${remote}`); // skip -u flag when pushing to URL (can't set upstream without a remote name)
const isUrl = remote.startsWith("https://");
const args = force
? ["push", "--force", ...(isUrl ? [] : ["-u"]), remote, branch]
: ["push", ...(isUrl ? [] : ["-u"]), remote, branch];
log.info(`Pushing branch ${branch} to ${isUrl ? "(fork URL)" : remote}`);
if (force) { if (force) {
log.warning(`Force pushing - this will overwrite remote history`); log.warning(`Force pushing - this will overwrite remote history`);
$("git", ["push", "--force", "-u", remote, branch]);
} else {
$("git", ["push", "-u", remote, branch]);
} }
$("git", args);
return { return {
success: true, success: true,
branch, branch,
remote, remote: isUrl ? "(fork URL)" : remote,
force, force,
message: `Successfully pushed branch ${branch} to ${remote}`, message: `Successfully pushed branch ${branch}`,
}; };
}), }),
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@pullfrog/action", "name": "@pullfrog/action",
"version": "0.0.142", "version": "0.0.143",
"type": "module", "type": "module",
"files": [ "files": [
"index.js", "index.js",
+30 -30
View File
@@ -68,11 +68,10 @@ export type SetupGitResult = {
/** /**
* Unified git setup: configures authentication and checks out PR branch if applicable. * Unified git setup: configures authentication and checks out PR branch if applicable.
* Uses gh as credential helper so git push works with any remote (including forks). * For fork PRs, returns a full URL to push to (since gh pr checkout doesn't set up remotes in Actions).
* For PR events, gh pr checkout sets up proper remote tracking. * For same-repo PRs, returns "origin".
* Returns the remote to push to (detected from branch tracking after checkout).
*/ */
export function setupGit(ctx: Context): SetupGitResult { export async function setupGit(ctx: Context): Promise<SetupGitResult> {
const repoDir = process.cwd(); const repoDir = process.cwd();
log.info("🔧 Setting up git authentication..."); log.info("🔧 Setting up git authentication...");
@@ -99,7 +98,7 @@ export function setupGit(ctx: Context): SetupGitResult {
return { pushRemote: "origin" }; return { pushRemote: "origin" };
} }
// checkout PR branch - gh pr checkout handles fork remotes and tracking automatically // checkout PR branch
const prNumber = ctx.payload.event.issue_number; const prNumber = ctx.payload.event.issue_number;
log.info(`🌿 Checking out PR #${prNumber}...`); log.info(`🌿 Checking out PR #${prNumber}...`);
$("gh", ["pr", "checkout", prNumber.toString()], { $("gh", ["pr", "checkout", prNumber.toString()], {
@@ -108,31 +107,32 @@ export function setupGit(ctx: Context): SetupGitResult {
}); });
log.info(`✓ Successfully checked out PR #${prNumber}`); log.info(`✓ Successfully checked out PR #${prNumber}`);
// detect the push remote from branch tracking (set by gh pr checkout) // check if this is a fork PR - gh pr checkout in Actions doesn't set up remotes for forks
const pushRemote = detectPushRemote(); const pr = await ctx.octokit.rest.pulls.get({
if (pushRemote !== "origin") { owner: ctx.owner,
log.info(`🍴 Fork PR detected, will push to remote: ${pushRemote}`); repo: ctx.name,
// embed token in fork remote URL too pull_number: prNumber,
const forkUrl = $("git", ["remote", "get-url", pushRemote], { cwd: repoDir, log: false }); });
const authedForkUrl = forkUrl.replace(
"https://github.com/",
`https://x-access-token:${ctx.githubInstallationToken}@github.com/`
);
$("git", ["remote", "set-url", pushRemote, authedForkUrl], { cwd: repoDir });
log.info(`✓ Updated ${pushRemote} URL with authentication token`);
}
return { pushRemote };
}
function detectPushRemote(): string { const headRepo = pr.data.head.repo;
try { const baseRepo = pr.data.base.repo;
const branch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
const upstream = $("git", ["rev-parse", "--abbrev-ref", `${branch}@{upstream}`], { // not a fork - push to origin
log: false, if (!headRepo || headRepo.full_name === baseRepo.full_name) {
}); return { pushRemote: "origin" };
// upstream is like "remote/branch", extract remote name
return upstream.split("/")[0];
} catch {
return "origin";
} }
// fork PR - return the full URL with auth token embedded
// git push accepts URLs directly, no remote needed
if (!pr.data.maintainer_can_modify) {
log.warning(
`⚠️ Fork PR from ${headRepo.owner.login} does not allow maintainer edits. Push may fail.`
);
}
// use GITHUB_TOKEN for fork push - it has the right permissions in Actions
const token = process.env.GITHUB_TOKEN || ctx.githubInstallationToken;
const forkUrl = `https://x-access-token:${token}@github.com/${headRepo.full_name}.git`;
log.info(`🍴 Fork PR detected, will push to: ${headRepo.full_name}`);
return { pushRemote: forkUrl };
} }