diff --git a/actions/setup/js/generate_git_patch.cjs b/actions/setup/js/generate_git_patch.cjs index 552e59311fd..63df67dcc93 100644 --- a/actions/setup/js/generate_git_patch.cjs +++ b/actions/setup/js/generate_git_patch.cjs @@ -3,17 +3,54 @@ const fs = require("fs"); const path = require("path"); -const { execSync } = require("child_process"); +const { promisify } = require("util"); +const { exec: execCallback } = require("child_process"); const { getBaseBranch } = require("./get_base_branch.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); +/** + * Execute a git command - works in both github-script and MCP server contexts + * @param {string} command - The git command to execute + * @param {Object} options - Options including cwd + * @returns {Promise<{stdout: string, stderr: string, exitCode: number}>} The result + */ +async function execGit(command, options = {}) { + const { cwd } = options; + + // When running in github-script context, exec is available as a global + if (typeof exec !== "undefined" && exec.getExecOutput) { + const args = command.split(" "); + const cmd = args[0]; + const cmdArgs = args.slice(1); + const result = await exec.getExecOutput(cmd, cmdArgs, { cwd, ignoreReturnCode: true }); + if (result.exitCode !== 0) { + const error = new Error(`Command failed: ${command}`); + // @ts-ignore - Adding exitCode property to Error + error.exitCode = result.exitCode; + throw error; + } + return result; + } + + // Fallback to promisified child_process.exec for MCP server context + const execAsync = promisify(execCallback); + try { + const result = await execAsync(command, { cwd }); + return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 }; + } catch (error) { + // @ts-ignore - Adding exitCode property to Error + error.exitCode = error.code || 1; + throw error; + } +} + /** * Generates a git patch file for the current changes * @param {string} branchName - The branch name to generate patch for - * @returns {Object} Object with patch info or error + * @returns {Promise} Object with patch info or error */ -function generateGitPatch(branchName) { +async function generateGitPatch(branchName) { const patchPath = "/tmp/gh-aw/aw.patch"; const cwd = process.env.GITHUB_WORKSPACE || process.cwd(); const defaultBranch = process.env.DEFAULT_BRANCH || getBaseBranch(); @@ -33,29 +70,34 @@ function generateGitPatch(branchName) { if (branchName) { // Check if the branch exists locally try { - execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + try { + await execGit(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd }); + } catch (showRefError) { + // Branch doesn't exist, skip to strategy 2 + throw showRefError; + } // Determine base ref for patch generation let baseRef; try { // Check if origin/branchName exists - execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + await execGit(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd }); baseRef = `origin/${branchName}`; } catch { // Use merge-base with default branch - execSync(`git fetch origin ${defaultBranch}`, { cwd, encoding: "utf8" }); - baseRef = execSync(`git merge-base origin/${defaultBranch} ${branchName}`, { cwd, encoding: "utf8" }).trim(); + await execGit(`git fetch origin ${defaultBranch}`, { cwd }); + const mergeBaseResult = await execGit(`git merge-base origin/${defaultBranch} ${branchName}`, { cwd }); + baseRef = mergeBaseResult.stdout.trim(); } // Count commits to be included - const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + const commitCountResult = await execGit(`git rev-list --count ${baseRef}..${branchName}`, { cwd }); + const commitCount = parseInt(commitCountResult.stdout.trim(), 10); if (commitCount > 0) { // Generate patch from the determined base to the branch - const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { - cwd, - encoding: "utf8", - }); + const patchContentResult = await execGit(`git format-patch ${baseRef}..${branchName} --stdout`, { cwd }); + const patchContent = patchContentResult.stdout; if (patchContent && patchContent.trim()) { fs.writeFileSync(patchPath, patchContent, "utf8"); @@ -69,7 +111,8 @@ function generateGitPatch(branchName) { // Strategy 2: Check if commits were made to current HEAD since checkout if (!patchGenerated) { - const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + const currentHeadResult = await execGit("git rev-parse HEAD", { cwd }); + const currentHead = currentHeadResult.stdout.trim(); if (!githubSha) { errorMessage = "GITHUB_SHA environment variable is not set"; @@ -78,17 +121,16 @@ function generateGitPatch(branchName) { } else { // Check if GITHUB_SHA is an ancestor of current HEAD try { - execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + await execGit(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd }); // Count commits between GITHUB_SHA and HEAD - const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + const commitCountResult = await execGit(`git rev-list --count ${githubSha}..HEAD`, { cwd }); + const commitCount = parseInt(commitCountResult.stdout.trim(), 10); if (commitCount > 0) { // Generate patch from GITHUB_SHA to HEAD - const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { - cwd, - encoding: "utf8", - }); + const patchContentResult = await execGit(`git format-patch ${githubSha}..HEAD --stdout`, { cwd }); + const patchContent = patchContentResult.stdout; if (patchContent && patchContent.trim()) { fs.writeFileSync(patchPath, patchContent, "utf8"); diff --git a/actions/setup/js/generate_git_patch.test.cjs b/actions/setup/js/generate_git_patch.test.cjs index 403838e4a80..3bcca457024 100644 --- a/actions/setup/js/generate_git_patch.test.cjs +++ b/actions/setup/js/generate_git_patch.test.cjs @@ -30,7 +30,7 @@ describe("generateGitPatch", () => { const { generateGitPatch } = await import("./generate_git_patch.cjs"); - const result = generateGitPatch(null); + const result = await generateGitPatch(null); expect(result.success).toBe(false); expect(result).toHaveProperty("error"); @@ -43,7 +43,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_WORKSPACE = "/tmp/nonexistent-repo"; process.env.GITHUB_SHA = "abc123"; - const result = generateGitPatch("nonexistent-branch"); + const result = await generateGitPatch("nonexistent-branch"); expect(result.success).toBe(false); expect(result).toHaveProperty("error"); @@ -57,7 +57,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_SHA = "abc123"; // Even if it fails, it should try to create the directory - const result = generateGitPatch("test-branch"); + const result = await generateGitPatch("test-branch"); expect(result).toHaveProperty("patchPath"); expect(result.patchPath).toBe("/tmp/gh-aw/aw.patch"); @@ -69,7 +69,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_WORKSPACE = "/tmp/nonexistent-repo"; process.env.GITHUB_SHA = "abc123"; - const result = generateGitPatch("test-branch"); + const result = await generateGitPatch("test-branch"); expect(result).toHaveProperty("success"); expect(result).toHaveProperty("patchPath"); @@ -82,7 +82,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_WORKSPACE = "/tmp/nonexistent-repo"; process.env.GITHUB_SHA = "abc123"; - const result = generateGitPatch(null); + const result = await generateGitPatch(null); expect(result).toHaveProperty("success"); expect(result).toHaveProperty("patchPath"); @@ -94,7 +94,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_WORKSPACE = "/tmp/nonexistent-repo"; process.env.GITHUB_SHA = "abc123"; - const result = generateGitPatch(""); + const result = await generateGitPatch(""); expect(result).toHaveProperty("success"); expect(result).toHaveProperty("patchPath"); @@ -107,7 +107,7 @@ describe("generateGitPatch", () => { process.env.GITHUB_SHA = "abc123"; process.env.DEFAULT_BRANCH = "develop"; - const result = generateGitPatch("feature-branch"); + const result = await generateGitPatch("feature-branch"); expect(result).toHaveProperty("success"); // Should attempt to use develop as default branch @@ -121,7 +121,7 @@ describe("generateGitPatch", () => { delete process.env.DEFAULT_BRANCH; process.env.GH_AW_BASE_BRANCH = "master"; - const result = generateGitPatch("feature-branch"); + const result = await generateGitPatch("feature-branch"); expect(result).toHaveProperty("success"); // Should attempt to use master as base branch diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 5e3425b9f4a..2d99dffb327 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -185,7 +185,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { * Resolves the current branch if branch is not provided or is the base branch * Generates git patch for the changes (unless allow-empty is true) */ - const createPullRequestHandler = args => { + const createPullRequestHandler = async args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -226,7 +226,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { // Generate git patch server.debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); - const patchResult = generateGitPatch(entry.branch); + const patchResult = await generateGitPatch(entry.branch); if (!patchResult.success) { // Patch generation failed or patch is empty @@ -276,7 +276,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { * Resolves the current branch if branch is not provided or is the base branch * Generates git patch for the changes */ - const pushToPullRequestBranchHandler = args => { + const pushToPullRequestBranchHandler = async args => { const entry = { ...args, type: "push_to_pull_request_branch" }; const baseBranch = getBaseBranch(); @@ -296,7 +296,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { // Generate git patch server.debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); - const patchResult = generateGitPatch(entry.branch); + const patchResult = await generateGitPatch(entry.branch); if (!patchResult.success) { // Patch generation failed or patch is empty