Spaces:
Paused
Paused
| import {mkdirSync, existsSync, rmSync, writeFileSync} from 'fs' | |
| import {join} from 'path' | |
| import {simpleGit, SimpleGit} from 'simple-git' | |
| import {getCurrentContext} from './context' | |
| import pino from 'pino' | |
| const logger = pino({ level: process.env.LOG_LEVEL || 'info' }) | |
| export interface CheckoutOptions { | |
| sparse?: boolean | |
| files?: string[] | |
| ref?: string | |
| } | |
| export const checkoutRepo = async ( | |
| tempDir: string, | |
| options: CheckoutOptions = {} | |
| ): Promise<string> => { | |
| const ctx = getCurrentContext() | |
| if (!ctx) throw new Error('No context available for checkout') | |
| // Verify git is available | |
| const git = simpleGit() | |
| try { | |
| await git.raw(['--version']) | |
| } catch (e) { | |
| throw new Error('git command not found in PATH. Please install git.') | |
| } | |
| const {owner, repo} = ctx.repo | |
| const installationId = ctx.probotContext.payload.installation?.id | |
| if (!installationId) { | |
| throw new Error('Installation ID not found in payload') | |
| } | |
| const {token} = (await ctx.probotContext.octokit.auth({ | |
| type: 'installation' | |
| })) as any | |
| if (!token) { | |
| throw new Error( | |
| 'Failed to create installation access token: token is undefined' | |
| ) | |
| } | |
| // Ensure gh CLI is authenticated for subsequent contexts | |
| process.env.GH_TOKEN = token | |
| process.env.GITHUB_TOKEN = token | |
| // SECURITY: Use token via environment variable, NOT in URL | |
| // This prevents token exposure in process listings and logs | |
| const cloneUrl = `https://github.com/${owner}/${repo}.git` | |
| const isPullRequest = !!ctx.probotContext.payload.pull_request | |
| const headBranch = isPullRequest | |
| ? ctx.probotContext.payload.pull_request.head.ref | |
| : (ctx.probotContext.payload.ref?.replace('refs/heads/', '') || ctx.probotContext.payload.repository?.default_branch || 'main') | |
| const headSha = isPullRequest | |
| ? ctx.probotContext.payload.pull_request.head.sha | |
| : (options.ref || ctx.probotContext.payload.after) | |
| // Set up git credential helper for secure token authentication | |
| process.env.GIT_ASKPASS = 'echo' | |
| process.env.GIT_USERNAME = 'x-access-token' | |
| process.env.GIT_PASSWORD = token | |
| if (existsSync(tempDir)) { | |
| rmSync(tempDir, {recursive: true, force: true}) | |
| } | |
| mkdirSync(tempDir, {recursive: true}) | |
| logger.info({ owner, repo, tempDir }, 'Cloning repository') | |
| const repoGit: SimpleGit = simpleGit(tempDir, { | |
| timeout: { block: 120000 } // 2 minute timeout for all git operations | |
| }) | |
| try { | |
| if (options.sparse && options.files && options.files.length > 0) { | |
| logger.info({ fileCount: options.files.length }, 'Using sparse checkout') | |
| // Initialize empty repo and configure sparse checkout | |
| await repoGit.init(['--bare']) | |
| await repoGit.addRemote('origin', cloneUrl) | |
| // Configure sparse checkout | |
| await repoGit.raw(['config', 'core.sparseCheckout', 'true']) | |
| writeFileSync(join(tempDir, '.git/info/sparse-checkout'), options.files.join('\n')) | |
| // Fetch and checkout specific commit | |
| await repoGit.fetch(['origin', headSha, '--depth', '1']) | |
| await repoGit.checkout(['FETCH_HEAD']) | |
| } else { | |
| logger.info('Performing full shallow clone') | |
| // Clone with depth 1 for efficiency | |
| await repoGit.clone(cloneUrl, '.', [ | |
| '--depth', '1', | |
| '--single-branch', | |
| '--branch', headBranch, | |
| '--no-checkout' | |
| ]) | |
| // Fetch and checkout specific SHA | |
| await repoGit.fetch(['origin', headSha, '--depth', '1']) | |
| await repoGit.checkout([headSha]) | |
| } | |
| // Configure git identity | |
| await repoGit.addConfig('user.name', 'PRIX AI Auditor') | |
| await repoGit.addConfig('user.email', 'bot@prix.ai') | |
| logger.info({ owner, repo, headSha }, 'Repository cloned successfully') | |
| } catch (error) { | |
| logger.error({ error, owner, repo }, 'Git operation failed') | |
| throw new Error(`Failed to clone repository: ${error}`) | |
| } | |
| return tempDir | |
| } | |