From ff375b97e435aa77382b5916fee511de7ae6ade3 Mon Sep 17 00:00:00 2001 From: David Blass Date: Tue, 25 Nov 2025 16:02:08 -0500 Subject: [PATCH] fix local git setup --- main.ts | 4 +- utils/setup.ts | 127 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 108 insertions(+), 23 deletions(-) diff --git a/main.ts b/main.ts index b3e3c87..17e23d4 100644 --- a/main.ts +++ b/main.ts @@ -20,7 +20,7 @@ import { revokeInstallationToken, setupGitHubInstallationToken, } from "./utils/github.ts"; -import { setupGitAuth, setupGitBranch, setupGitConfig } from "./utils/setup.ts"; +import { restoreGitConfig, setupGitAuth, setupGitBranch, setupGitConfig } from "./utils/setup.ts"; // runtime validation using agents (needed for ArkType) // Note: The AgentName type is defined in external.ts, this is the runtime validator @@ -96,6 +96,8 @@ export async function main(inputs: Inputs): Promise { error: errorMessage, }; } finally { + restoreGitConfig(); + if (pollInterval) { clearInterval(pollInterval); } diff --git a/utils/setup.ts b/utils/setup.ts index fd332a9..a684706 100644 --- a/utils/setup.ts +++ b/utils/setup.ts @@ -5,6 +5,9 @@ import { log } from "./cli.ts"; import type { RepoContext } from "./github.ts"; import { $ } from "./shell.ts"; +// Store original remote URL for cleanup (only thing we need to restore) +let originalRemoteUrl: string | null = null; + export interface SetupOptions { tempDir: string; repoUrl?: string; @@ -43,20 +46,22 @@ export function setupTestRepo(options: SetupOptions): void { /** * Setup git configuration to avoid identity errors - * Only runs in GitHub Actions environment to avoid overwriting local git config + * Uses --local flag to scope config to the current repo only */ export function setupGitConfig(): void { - // Only set up git config in GitHub Actions environment - // In local development, use the user's existing git config - if (!process.env.GITHUB_ACTIONS) { - return; - } - + const repoDir = process.cwd(); log.info("🔧 Setting up git configuration..."); try { - execSync('git config user.email "action@pullfrog.ai"', { stdio: "pipe" }); - execSync('git config user.name "Pullfrog Action"', { stdio: "pipe" }); - log.debug("setupGitConfig: ✓ Git configuration set successfully"); + // Use --local to scope config to this repo only, preventing leakage to user's global config + execSync('git config --local user.email "action@pullfrog.ai"', { + cwd: repoDir, + stdio: "pipe", + }); + execSync('git config --local user.name "Pullfrog Action"', { + cwd: repoDir, + stdio: "pipe", + }); + log.debug("setupGitConfig: ✓ Git configuration set successfully (scoped to repo)"); } catch (error) { // If git config fails, log warning but don't fail the action // This can happen if we're not in a git repo or git isn't available @@ -67,30 +72,43 @@ export function setupGitConfig(): void { } /** - * Setup git authentication using GitHub token - * Only runs in GitHub Actions environment to avoid breaking local git remotes + * Setup git authentication using GitHub installation token + * Always uses the installation token, scoped to the current repo only */ export function setupGitAuth(githubToken: string, repoContext: RepoContext): void { - // Only set up git auth in GitHub Actions environment - // In local testing, this would overwrite the real git remote with fake credentials - if (!process.env.GITHUB_ACTIONS) { - return; + const repoDir = process.cwd(); + + // Store original remote URL for cleanup + try { + originalRemoteUrl = + execSync("git config --local --get remote.origin.url", { + cwd: repoDir, + stdio: "pipe", + encoding: "utf-8", + }).trim() || null; + } catch { + originalRemoteUrl = null; } log.info("🔐 Setting up git authentication..."); // Remove existing git auth headers that actions/checkout might have set + // Use --local to scope to this repo only try { - execSync("git config --unset-all http.https://github.com/.extraheader", { stdio: "inherit" }); + execSync("git config --local --unset-all http.https://github.com/.extraheader", { + cwd: repoDir, + stdio: "pipe", + }); log.info("✓ Removed existing authentication headers"); } catch { - log.info("No existing authentication headers to remove"); + log.debug("No existing authentication headers to remove"); } // Update remote URL to embed the token + // This is scoped to the repo's .git/config, not the user's global config const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoContext.owner}/${repoContext.name}.git`; - $("git", ["remote", "set-url", "origin", remoteUrl]); - log.info("✓ Updated remote URL with authentication token"); + $("git", ["remote", "set-url", "origin", remoteUrl], { cwd: repoDir }); + log.info("✓ Updated remote URL with authentication token (scoped to repo)"); } /** @@ -99,6 +117,7 @@ export function setupGitAuth(githubToken: string, repoContext: RepoContext): voi */ export function setupGitBranch(payload: Payload): void { const branch = payload.event.branch; + const repoDir = process.cwd(); if (!branch) { log.debug("No branch specified in payload, using default branch"); @@ -110,11 +129,17 @@ export function setupGitBranch(payload: Payload): void { try { // Fetch the branch from origin log.debug(`Fetching branch from origin: ${branch}`); - execSync(`git fetch origin ${branch}`, { stdio: "pipe" }); + execSync(`git fetch origin ${branch}`, { + cwd: repoDir, + stdio: "pipe", + }); // Checkout the branch, creating local tracking branch log.debug(`Checking out branch: ${branch}`); - execSync(`git checkout -B ${branch} origin/${branch}`, { stdio: "pipe" }); + execSync(`git checkout -B ${branch} origin/${branch}`, { + cwd: repoDir, + stdio: "pipe", + }); log.info(`✓ Successfully checked out branch: ${branch}`); } catch (error) { @@ -125,3 +150,61 @@ export function setupGitBranch(payload: Payload): void { ); } } + +/** + * Clean up local git configuration after action completes + * Removes the --local config entries we added so the repo returns to its original state + * Only runs in local development (not in GitHub Actions) + */ +export function restoreGitConfig(): void { + if (process.env.GITHUB_ACTIONS) { + return; + } + + const repoDir = process.cwd(); + log.info("🔄 Cleaning up git configuration..."); + + try { + try { + execSync("git config --local --unset user.email", { + cwd: repoDir, + stdio: "pipe", + }); + log.debug("✓ Removed local user.email"); + } catch { + // Ignore if unset fails (config might not exist) + } + + try { + execSync("git config --local --unset user.name", { + cwd: repoDir, + stdio: "pipe", + }); + log.debug("✓ Removed local user.name"); + } catch { + // Ignore if unset fails (config might not exist) + } + + // Restore original remote URL if we stored it + if (originalRemoteUrl !== null) { + try { + $("git", ["remote", "set-url", "origin", originalRemoteUrl], { cwd: repoDir }); + log.debug("✓ Restored original remote URL"); + } catch (error) { + log.warning( + `Failed to restore remote URL: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + + log.info("✓ Git configuration cleanup completed"); + } catch (error) { + // Log warning but don't fail - this is cleanup + log.warning( + `Failed to clean up git config: ${error instanceof Error ? error.message : String(error)}` + ); + } finally { + // Clear stored remote URL + originalRemoteUrl = null; + } +}