diff --git a/.changeset/patch-refactor-patch-generation-to-mcp-server.md b/.changeset/patch-refactor-patch-generation-to-mcp-server.md new file mode 100644 index 000000000..76fb219c4 --- /dev/null +++ b/.changeset/patch-refactor-patch-generation-to-mcp-server.md @@ -0,0 +1,7 @@ +--- +"gh-aw": patch +--- + +Refactor patch generation from workflow step to MCP server + +Moves git patch generation from a dedicated workflow step to the safe-outputs MCP server, where it executes when `create_pull_request` or `push_to_pull_request_branch` tools are called. This provides immediate error feedback when no changes exist, rather than discovering it later in processing jobs. diff --git a/.github/workflows/ai-triage-campaign.lock.yml b/.github/workflows/ai-triage-campaign.lock.yml index b55a139fe..b66485068 100644 --- a/.github/workflows/ai-triage-campaign.lock.yml +++ b/.github/workflows/ai-triage-campaign.lock.yml @@ -826,6 +826,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -838,12 +961,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -860,12 +998,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 8b1f59b0b..ec241060e 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -1858,6 +1858,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1870,12 +1993,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1892,12 +2030,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index ff4cc41e1..4a0f9f856 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -760,6 +760,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -772,12 +895,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -794,12 +932,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index b2a91955e..ad1ff73b3 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1521,6 +1521,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1533,12 +1656,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1555,12 +1693,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 26ed9b46a..6521a3c5c 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -1042,6 +1042,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1054,12 +1177,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1076,12 +1214,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index baf3c9428..dc8851f5b 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -1731,6 +1731,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1743,12 +1866,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1765,12 +1903,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 93e6225cc..aa1fb8532 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -1475,6 +1475,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1487,12 +1610,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1509,12 +1647,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5005,205 +5158,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 030237f3f..d63978526 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1206,6 +1206,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1218,12 +1341,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1240,12 +1378,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 6236f1111..0014a17a5 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -771,6 +771,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -783,12 +906,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -805,12 +943,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 51fe5fdf6..95d766a93 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -936,6 +936,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -948,12 +1071,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -970,12 +1108,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 0ac3c2358..728468240 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -2090,6 +2090,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -2102,12 +2225,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -2124,12 +2262,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5065,205 +5218,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 900cde509..b34335b7e 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -1013,6 +1013,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1025,12 +1148,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1047,12 +1185,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 9acb85d2b..b81bf4aef 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1418,6 +1418,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1430,12 +1553,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1452,12 +1590,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index b5d5d0559..427f1927e 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1568,6 +1568,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1580,12 +1703,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1602,12 +1740,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 05e523ee3..d9d238da0 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1141,6 +1141,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1153,12 +1276,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1175,12 +1313,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 71d1d3f03..25aec9d78 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -2251,6 +2251,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -2263,12 +2386,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -2285,12 +2423,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 821f36896..0cf7fa920 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -1884,6 +1884,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1896,12 +2019,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1918,12 +2056,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5089,205 +5242,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index ea4fe7b97..48518fb1a 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -1596,6 +1596,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1608,12 +1731,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1630,12 +1768,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 02fe57801..4ea0e78f1 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -883,6 +883,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -895,12 +1018,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -917,12 +1055,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -3666,205 +3819,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index af52e66b8..b32e8c049 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -942,6 +942,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -954,12 +1077,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -976,12 +1114,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 85e5f461a..56328bff7 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -1269,6 +1269,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1281,12 +1404,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1303,12 +1441,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 248492b82..47a2d5dc0 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -805,6 +805,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -817,12 +940,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -839,12 +977,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 240158077..5c37131dc 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1444,6 +1444,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1456,12 +1579,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1478,12 +1616,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 2bb57152b..0a36b2bad 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -1314,6 +1314,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1326,12 +1449,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1348,12 +1486,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 3cefb6e7b..998fcfb46 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -705,6 +705,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -717,12 +840,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -739,12 +877,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 015a7b014..62419af02 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -1018,6 +1018,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1030,12 +1153,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1052,12 +1190,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 7e265b62b..a4ab4456c 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -1127,6 +1127,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1139,12 +1262,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1161,12 +1299,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 538d8bbb8..8857629ce 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -606,6 +606,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -618,12 +741,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -640,12 +778,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 6edf32dbc..0ba6ac2de 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -1415,6 +1415,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1427,12 +1550,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1449,12 +1587,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -4714,205 +4867,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 0bc5ff0b8..cea0af708 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -742,6 +742,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -754,12 +877,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -776,12 +914,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -3815,205 +3968,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index a862798a7..97b0cb8ce 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -766,6 +766,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -778,12 +901,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -800,12 +938,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index cc830b360..668028673 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -849,6 +849,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -861,12 +984,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -883,12 +1021,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 2e05e4053..3502657d4 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -817,6 +817,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -829,12 +952,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -851,12 +989,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 4e954dfec..a2ee1f877 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -1263,6 +1263,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1275,12 +1398,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1297,12 +1435,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -4408,205 +4561,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index eb26832e1..e157ce318 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1266,6 +1266,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1278,12 +1401,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1300,12 +1438,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5254,205 +5407,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 1a103fc59..bb9eca2fc 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -999,6 +999,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1011,12 +1134,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1033,12 +1171,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -3884,205 +4037,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 3205dce28..bd680dc8a 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -847,6 +847,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -859,12 +982,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -881,12 +1019,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index f412cd864..0a7b32b6a 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -1779,6 +1779,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1791,12 +1914,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1813,12 +1951,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index b347d051d..d9ed9ba2d 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -882,6 +882,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -894,12 +1017,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -916,12 +1054,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -3663,205 +3816,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index e675f9569..6bbc646a2 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -1526,6 +1526,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1538,12 +1661,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1560,12 +1698,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 2afacaeb3..3861ebf98 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -1135,6 +1135,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1147,12 +1270,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1169,12 +1307,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index b99dc2c1d..9753d4f60 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -1272,6 +1272,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1284,12 +1407,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1306,12 +1444,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 83c7fa6f0..377e1370b 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -1229,6 +1229,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1241,12 +1364,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1263,12 +1401,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -4513,205 +4666,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 05c984f5d..ab92e337b 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -626,6 +626,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -638,12 +761,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -660,12 +798,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index b568ff679..8f7ea7da7 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -1820,6 +1820,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1832,12 +1955,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1854,12 +1992,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 8edb72dfa..00ae807f0 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -1341,6 +1341,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1353,12 +1476,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1375,12 +1513,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index a1a614c37..eb6ed75a4 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -2016,6 +2016,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -2028,12 +2151,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -2050,12 +2188,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5112,205 +5265,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 3ccffb39f..1bc45f7cf 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -1837,6 +1837,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1849,12 +1972,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1871,12 +2009,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 9ac6f7f82..998d83e63 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -1812,6 +1812,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1824,12 +1947,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1846,12 +1984,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 3eff3da89..a5f984418 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -1633,6 +1633,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1645,12 +1768,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1667,12 +1805,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 5bf1e7072..0433376d1 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -2097,6 +2097,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -2109,12 +2232,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -2131,12 +2269,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5506,205 +5659,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/release-highlights.lock.yml b/.github/workflows/release-highlights.lock.yml index 48f28a77f..8d4926609 100644 --- a/.github/workflows/release-highlights.lock.yml +++ b/.github/workflows/release-highlights.lock.yml @@ -826,6 +826,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -838,12 +961,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -860,12 +998,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index d327f6f68..eb6da581a 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -786,6 +786,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -798,12 +921,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -820,12 +958,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 70282da65..c1363aa04 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -1254,6 +1254,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1266,12 +1389,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1288,12 +1426,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 784fc2e26..0dc62e9fb 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -710,6 +710,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -722,12 +845,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -744,12 +882,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 6eb6fd15a..a1127939e 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -1252,6 +1252,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1264,12 +1387,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1286,12 +1424,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 95bb536c8..e125328df 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -1139,6 +1139,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1151,12 +1274,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1173,12 +1311,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 948ca9b2a..f5f991e96 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -2171,6 +2171,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -2183,12 +2306,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -2205,12 +2343,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index e9ea2f669..43002fc28 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -870,6 +870,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -882,12 +1005,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -904,12 +1042,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -3609,205 +3762,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index c1a93b7e9..d3fee746c 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -1250,6 +1250,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1262,12 +1385,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1284,12 +1422,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 86ac9d585..95aed2c82 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1254,6 +1254,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1266,12 +1389,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1288,12 +1426,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 8a6d29117..8dd7447e2 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1030,6 +1030,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1042,12 +1165,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1064,12 +1202,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 0c2a89d93..e40a61bb5 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1038,6 +1038,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1050,12 +1173,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1072,12 +1210,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 7de714c43..56a63f7c0 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -1845,6 +1845,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1857,12 +1980,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1879,12 +2017,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index ea8f93f80..0cf24a70e 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -1163,6 +1163,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1175,12 +1298,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1197,12 +1335,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index 840b85161..647e21201 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -824,6 +824,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -836,12 +959,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -858,12 +996,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index c8b5a1b0f..d47f3216d 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -1468,6 +1468,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1480,12 +1603,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1502,12 +1640,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -5221,205 +5374,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/test-assign-milestone-allowed.lock.yml b/.github/workflows/test-assign-milestone-allowed.lock.yml index 5ad710feb..f96170976 100644 --- a/.github/workflows/test-assign-milestone-allowed.lock.yml +++ b/.github/workflows/test-assign-milestone-allowed.lock.yml @@ -705,6 +705,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -717,12 +840,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -739,12 +877,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/test-claude-assign-milestone.lock.yml b/.github/workflows/test-claude-assign-milestone.lock.yml index 4285fc6c5..83bbc9ee0 100644 --- a/.github/workflows/test-claude-assign-milestone.lock.yml +++ b/.github/workflows/test-claude-assign-milestone.lock.yml @@ -699,6 +699,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -711,12 +834,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -733,12 +871,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/test-close-discussion.lock.yml b/.github/workflows/test-close-discussion.lock.yml index 46309cf47..b59b256d2 100644 --- a/.github/workflows/test-close-discussion.lock.yml +++ b/.github/workflows/test-close-discussion.lock.yml @@ -611,6 +611,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -623,12 +746,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -645,12 +783,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/test-codex-assign-milestone.lock.yml b/.github/workflows/test-codex-assign-milestone.lock.yml index 8908ccd10..14b0285bb 100644 --- a/.github/workflows/test-codex-assign-milestone.lock.yml +++ b/.github/workflows/test-codex-assign-milestone.lock.yml @@ -590,6 +590,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -602,12 +725,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -624,12 +762,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/test-copilot-assign-milestone.lock.yml b/.github/workflows/test-copilot-assign-milestone.lock.yml index 2df7ce496..f1c59947e 100644 --- a/.github/workflows/test-copilot-assign-milestone.lock.yml +++ b/.github/workflows/test-copilot-assign-milestone.lock.yml @@ -590,6 +590,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -602,12 +725,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -624,12 +762,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index b2b056690..32c0d71ad 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -596,6 +596,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -608,12 +731,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -630,12 +768,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 6190c6bac..f144c7787 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -1040,6 +1040,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1052,12 +1175,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1074,12 +1212,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -4099,205 +4252,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index 42bd93406..297af2f90 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -1279,6 +1279,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1291,12 +1414,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1313,12 +1451,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 89b641be7..2b2547e31 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -1823,6 +1823,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1835,12 +1958,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1857,12 +1995,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -4845,205 +4998,6 @@ jobs: if (typeof module === "undefined" || require.main === module) { main(); } - - name: Generate git patch - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_SHA: ${{ github.sha }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - # Diagnostic logging: Show environment information - echo "=== Diagnostic: Environment Information ===" - echo "GITHUB_SHA: ${GITHUB_SHA}" - echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" - echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" - echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" - # Diagnostic logging: Show recent commits before patch generation - echo "" - echo "=== Diagnostic: Recent commits (last 10) ===" - git log --oneline -10 || echo "Failed to show git log" - # Check current git status - echo "" - echo "=== Diagnostic: Current git status ===" - git status - # Extract branch name from JSONL output - BRANCH_NAME="" - if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then - echo "" - echo "Checking for branch name in JSONL output..." - echo "JSONL file path: $GH_AW_SAFE_OUTPUTS" - while IFS= read -r line; do - if [ -n "$line" ]; then - # Extract branch from create-pull-request line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then - echo "Found create_pull_request line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" - break - fi - # Extract branch from push_to_pull_request_branch line using simple grep and sed - # Note: types use underscores (normalized by safe-outputs MCP server) - elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then - echo "Found push_to_pull_request_branch line: $line" - # Extract branch value using sed - BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" - if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" - break - fi - fi - fi - done < "$GH_AW_SAFE_OUTPUTS" - else - echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" - fi - # If no branch found in JSONL, log it but don't give up yet - if [ -z "$BRANCH_NAME" ]; then - echo "" - echo "No branch name found in JSONL output" - echo "Will check for commits made to current HEAD instead" - fi - # Strategy 1: If we have a branch name, check if that branch exists and get its diff - PATCH_GENERATED=false - if [ -n "$BRANCH_NAME" ]; then - echo "" - echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" - # Check if the branch exists - if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" - # Check if origin/$BRANCH_NAME exists to use as base - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" - BASE_REF="origin/$BRANCH_NAME" - else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" - # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" - # Fetch the default branch to ensure it's available locally - git fetch origin "$DEFAULT_BRANCH" - # Find merge base between default branch and current branch - BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" - fi - # Diagnostic logging: Show diff stats before generating patch - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" - git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" - # Diagnostic logging: Count commits to be included - echo "" - echo "=== Diagnostic: Commits to be included in patch ===" - COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits" - fi - # Diagnostic logging: Show the exact command being used - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" - # Generate patch from the determined base to the branch - git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" - PATCH_GENERATED=true - else - echo "Branch $BRANCH_NAME does not exist locally" - fi - fi - # Strategy 2: Check if commits were made to current HEAD since checkout - if [ "$PATCH_GENERATED" = false ]; then - echo "" - echo "=== Strategy 2: Checking for commits on current HEAD ===" - # Get current HEAD SHA - CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" - if [ -z "$CURRENT_HEAD" ]; then - echo "ERROR: Could not determine current HEAD SHA" - elif [ -z "$GITHUB_SHA" ]; then - echo "ERROR: GITHUB_SHA environment variable is not set" - elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then - echo "No commits have been made since checkout (HEAD == GITHUB_SHA)" - echo "No patch will be generated" - else - echo "HEAD has moved since checkout - checking if commits were added" - # Check if GITHUB_SHA is an ancestor of current HEAD - if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then - echo "GITHUB_SHA is an ancestor of HEAD - commits were added" - # Count commits between GITHUB_SHA and HEAD - COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")" - echo "" - echo "=== Diagnostic: Commits added since checkout ===" - echo "Number of commits: $COMMIT_COUNT" - if [ "$COMMIT_COUNT" -gt 0 ]; then - echo "Commit SHAs:" - git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits" - # Show diff stats - echo "" - echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" - git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" - # Generate patch from GITHUB_SHA to HEAD - echo "" - echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" - git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" - PATCH_GENERATED=true - else - echo "No commits found between GITHUB_SHA and HEAD" - fi - else - echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged" - echo "This may indicate a rebase or other history rewriting operation" - echo "Will not generate patch due to ambiguous history" - fi - fi - fi - # Final status - echo "" - if [ "$PATCH_GENERATED" = true ]; then - echo "=== Patch generation completed successfully ===" - else - echo "=== No patch generated ===" - echo "Reason: No commits found via branch name or HEAD analysis" - fi - # Show patch info if it exists - if [ -f /tmp/gh-aw/aw.patch ]; then - echo "" - echo "=== Diagnostic: Patch file information ===" - ls -lh /tmp/gh-aw/aw.patch - # Get patch file size in KB - PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)" - echo "Patch file size: ${PATCH_SIZE} KB" - # Count lines in patch - PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)" - echo "Patch file lines: $PATCH_LINES" - # Extract and count commits from patch file (each commit starts with "From ") - PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")" - echo "Commits included in patch: $PATCH_COMMITS" - # List commit SHAs in the patch - if [ "$PATCH_COMMITS" -gt 0 ]; then - echo "Commit SHAs in patch:" - grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs" - fi - # Show the first 50 lines of the patch for review - { - echo '## Git Patch' - echo '' - echo '```diff' - head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents" - echo '...' - echo '```' - echo '' - } >> "$GITHUB_STEP_SUMMARY" - fi - name: Upload git patch if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 53ac5265d..a66035e12 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -870,6 +870,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -882,12 +1005,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -904,12 +1042,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 4128250b0..eb39c3911 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -1222,6 +1222,129 @@ jobs: function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } + 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(); + const githubSha = process.env.GITHUB_SHA; + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + let patchGenerated = false; + let errorMessage = null; + try { + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + let baseRef; + try { + execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { cwd, encoding: "utf8" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + if (commitCount > 0) { + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + if (!patchContent.trim()) { + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; + } const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; const baseBranch = getBaseBranch(); @@ -1234,12 +1357,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -1256,12 +1394,27 @@ jobs: } entry.branch = detectedBranch; } + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + if (!patchResult.success) { + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/pkg/workflow/compiler_yaml.go b/pkg/workflow/compiler_yaml.go index f6a13ae30..9b8b8ac04 100644 --- a/pkg/workflow/compiler_yaml.go +++ b/pkg/workflow/compiler_yaml.go @@ -394,9 +394,12 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat // Add error validation for AI execution logs c.generateErrorValidation(yaml, engine, data) - // Add git patch generation step only if safe-outputs create-pull-request feature is used + // NOTE: Git patch generation has been moved to the safe-outputs MCP server + // The patch is now generated when create_pull_request or push_to_pull_request_branch + // tools are called, providing immediate error feedback if no changes are present. + // We still upload the patch artifact for processing jobs to download. if data.SafeOutputs != nil && (data.SafeOutputs.CreatePullRequests != nil || data.SafeOutputs.PushToPullRequestBranch != nil) { - c.generateGitPatchStep(yaml) + c.generateGitPatchUploadStep(yaml) } // Add post-steps (if any) after AI execution diff --git a/pkg/workflow/git_patch.go b/pkg/workflow/git_patch.go index 30e8cf598..fcea0bac5 100644 --- a/pkg/workflow/git_patch.go +++ b/pkg/workflow/git_patch.go @@ -5,16 +5,10 @@ import ( "strings" ) -// generateGitPatchStep generates a step that creates and uploads a git patch of changes -func (c *Compiler) generateGitPatchStep(yaml *strings.Builder) { - yaml.WriteString(" - name: Generate git patch\n") - yaml.WriteString(" if: always()\n") - yaml.WriteString(" env:\n") - yaml.WriteString(" GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}\n") - yaml.WriteString(" GITHUB_SHA: ${{ github.sha }}\n") - yaml.WriteString(" DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}\n") - yaml.WriteString(" run: |\n") - WriteShellScriptToYAML(yaml, generateGitPatchScript, " ") +// generateGitPatchUploadStep generates a step that uploads a git patch artifact +// The patch itself is generated by the safe-outputs MCP server when create_pull_request +// or push_to_pull_request_branch tools are called. +func (c *Compiler) generateGitPatchUploadStep(yaml *strings.Builder) { yaml.WriteString(" - name: Upload git patch\n") yaml.WriteString(" if: always()\n") yaml.WriteString(fmt.Sprintf(" uses: %s\n", GetActionPin("actions/upload-artifact"))) diff --git a/pkg/workflow/git_patch_test.go b/pkg/workflow/git_patch_test.go index 69cef781b..ef27fee01 100644 --- a/pkg/workflow/git_patch_test.go +++ b/pkg/workflow/git_patch_test.go @@ -68,30 +68,39 @@ Please do the following tasks: lockContent := string(content) - // Verify git patch generation step exists - if !strings.Contains(lockContent, "- name: Generate git patch") { - t.Error("Expected 'Generate git patch' step to be in generated workflow") - } + // NOTE: Git patch generation has been moved to the safe-outputs MCP server + // The patch is now generated when create_pull_request or push_to_pull_request_branch + // tools are called within the MCP server, not as a separate workflow step. - // Verify the git patch step contains the expected commands - if !strings.Contains(lockContent, "git status") { - t.Error("Expected 'git status' command in git patch step") + // Verify git patch generation step does NOT exist in main job anymore + if strings.Contains(lockContent, "- name: Generate git patch") { + t.Error("Did not expect 'Generate git patch' step in main job (now handled by MCP server)") } - if !strings.Contains(lockContent, "git format-patch") { - t.Error("Expected 'git format-patch' command in git patch step") + // The patch generation script commands should NOT be in the main job + // Note: git commands may still appear elsewhere in the workflow (e.g., checkout, git config) + mainJobIndex := strings.Index(lockContent, "execute_agentic_workflow:") + createPRJobIndex := strings.Index(lockContent, "create_pull_request:") + + var mainJobContent string + if mainJobIndex != -1 && createPRJobIndex != -1 { + mainJobContent = lockContent[mainJobIndex:createPRJobIndex] + } else if mainJobIndex != -1 { + mainJobContent = lockContent[mainJobIndex:] } - if !strings.Contains(lockContent, "/tmp/gh-aw/aw.patch") { - t.Error("Expected '/tmp/gh-aw/aw.patch' path in git patch step") + // Check that the main job doesn't have patch generation commands + if strings.Contains(mainJobContent, "git format-patch") { + t.Error("Did not expect 'git format-patch' command in main job (now handled by MCP server)") } - // Verify git patch upload step exists + // Verify git patch upload step still exists (for backward compatibility with processing jobs) + // This step uploads the patch artifact that was generated by the MCP server if !strings.Contains(lockContent, "- name: Upload git patch") { t.Error("Expected 'Upload git patch' step to be in generated workflow") } - // Verify the upload step uses actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + // Verify the upload step uses actions/upload-artifact if !strings.Contains(lockContent, "uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4") { t.Error("Expected upload-artifact action to be used for git patch upload step") } @@ -109,24 +118,6 @@ Please do the following tasks: t.Error("Expected 'if-no-files-found: ignore' in upload step") } - // Verify the git patch step runs with 'if: always()' - gitPatchIndex := strings.Index(lockContent, "- name: Generate git patch") - if gitPatchIndex == -1 { - t.Fatal("Git patch step not found") - } - - // Find the next step after git patch step - nextStepStart := gitPatchIndex + len("- name: Generate git patch") - stepEnd := strings.Index(lockContent[nextStepStart:], "- name:") - if stepEnd == -1 { - stepEnd = len(lockContent) - nextStepStart - } - gitPatchStep := lockContent[gitPatchIndex : nextStepStart+stepEnd] - - if !strings.Contains(gitPatchStep, "if: always()") { - t.Error("Expected git patch step to have 'if: always()' condition") - } - // Verify the upload step runs with conditional logic for file existence uploadPatchIndex := strings.Index(lockContent, "- name: Upload git patch") if uploadPatchIndex == -1 { @@ -145,25 +136,5 @@ Please do the following tasks: t.Error("Expected upload git patch step to have 'if: always()' condition") } - // Verify step ordering: git patch steps should be after agentic execution but before other uploads - agenticIndex := strings.Index(lockContent, "Execute Claude Code") - if agenticIndex == -1 { - // Try alternative agentic step names - agenticIndex = strings.Index(lockContent, "npx @anthropic-ai/claude-code") - if agenticIndex == -1 { - agenticIndex = strings.Index(lockContent, "uses: githubnext/claude-action") - } - } - - uploadEngineLogsIndex := strings.Index(lockContent, "Upload agentic engine logs") - - if agenticIndex != -1 && gitPatchIndex != -1 && uploadEngineLogsIndex != -1 { - if gitPatchIndex <= agenticIndex { - t.Error("Git patch step should appear after agentic execution step") - } - - if gitPatchIndex >= uploadEngineLogsIndex { - t.Error("Git patch step should appear before engine logs upload step") - } - } + t.Logf("Successfully verified git patch workflow (patch now generated in MCP server)") } diff --git a/pkg/workflow/js/safe_outputs_mcp_server.cjs b/pkg/workflow/js/safe_outputs_mcp_server.cjs index 9cee27e51..2263a94fc 100644 --- a/pkg/workflow/js/safe_outputs_mcp_server.cjs +++ b/pkg/workflow/js/safe_outputs_mcp_server.cjs @@ -475,9 +475,174 @@ function getBaseBranch() { return process.env.GH_AW_BASE_BRANCH || "main"; } +/** + * 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 + */ +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(); + const githubSha = process.env.GITHUB_SHA; + + debug(`Generating git patch for branch: ${branchName}`); + debug(`Working directory: ${cwd}`); + debug(`Default branch: ${defaultBranch}`); + debug(`GITHUB_SHA: ${githubSha}`); + + // Ensure /tmp/gh-aw directory exists + const patchDir = path.dirname(patchPath); + if (!fs.existsSync(patchDir)) { + fs.mkdirSync(patchDir, { recursive: true }); + } + + let patchGenerated = false; + let errorMessage = null; + + try { + // Strategy 1: If we have a branch name, check if that branch exists and get its diff + if (branchName) { + debug(`Strategy 1: Using named branch: ${branchName}`); + + // Check if the branch exists locally + try { + execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { cwd, encoding: "utf8" }); + debug(`Branch ${branchName} exists locally`); + + // 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" }); + baseRef = `origin/${branchName}`; + debug(`Using origin/${branchName} as base for patch generation`); + } catch { + // Use merge-base with default branch + debug(`origin/${branchName} does not exist, using 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(); + debug(`Using merge-base as base: ${baseRef}`); + } + + // Count commits to be included + const commitCount = parseInt(execSync(`git rev-list --count ${baseRef}..${branchName}`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits in patch: ${commitCount}`); + + if (commitCount > 0) { + // Generate patch from the determined base to the branch + const patchContent = execSync(`git format-patch ${baseRef}..${branchName} --stdout`, { + cwd, + encoding: "utf8", + }); + + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from branch: ${branchName} (base: ${baseRef})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between ${baseRef} and ${branchName}`); + } + } catch (branchError) { + debug(`Branch ${branchName} does not exist locally: ${branchError instanceof Error ? branchError.message : String(branchError)}`); + } + } + + // Strategy 2: Check if commits were made to current HEAD since checkout + if (!patchGenerated) { + debug(`Strategy 2: Checking for commits on current HEAD`); + + const currentHead = execSync("git rev-parse HEAD", { cwd, encoding: "utf8" }).trim(); + debug(`Current HEAD: ${currentHead}`); + debug(`Checkout SHA (GITHUB_SHA): ${githubSha}`); + + if (!githubSha) { + errorMessage = "GITHUB_SHA environment variable is not set"; + debug(`ERROR: ${errorMessage}`); + } else if (currentHead === githubSha) { + debug("No commits have been made since checkout (HEAD == GITHUB_SHA)"); + } else { + // Check if GITHUB_SHA is an ancestor of current HEAD + try { + execSync(`git merge-base --is-ancestor ${githubSha} HEAD`, { cwd, encoding: "utf8" }); + debug("GITHUB_SHA is an ancestor of HEAD - commits were added"); + + // Count commits between GITHUB_SHA and HEAD + const commitCount = parseInt(execSync(`git rev-list --count ${githubSha}..HEAD`, { cwd, encoding: "utf8" }).trim(), 10); + debug(`Number of commits added since checkout: ${commitCount}`); + + if (commitCount > 0) { + // Generate patch from GITHUB_SHA to HEAD + const patchContent = execSync(`git format-patch ${githubSha}..HEAD --stdout`, { + cwd, + encoding: "utf8", + }); + + if (patchContent && patchContent.trim()) { + fs.writeFileSync(patchPath, patchContent, "utf8"); + debug(`Patch file created from commits on HEAD (base: ${githubSha})`); + patchGenerated = true; + } else { + debug(`No patch content generated (empty diff)`); + } + } else { + debug(`No commits found between GITHUB_SHA and HEAD`); + } + } catch { + debug("GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"); + } + } + } + } catch (error) { + errorMessage = `Failed to generate patch: ${error instanceof Error ? error.message : String(error)}`; + debug(`ERROR: ${errorMessage}`); + } + + // Check if patch was generated and has content + if (patchGenerated && fs.existsSync(patchPath)) { + const patchContent = fs.readFileSync(patchPath, "utf8"); + const patchSize = Buffer.byteLength(patchContent, "utf8"); + const patchLines = patchContent.split("\n").length; + + if (!patchContent.trim()) { + // Empty patch + debug("Patch file is empty - no changes to commit"); + return { + success: false, + error: "No changes to commit - patch is empty", + patchPath: patchPath, + patchSize: 0, + patchLines: 0, + }; + } + + debug(`Patch file created: ${patchPath}`); + debug(`Patch size: ${patchSize} bytes`); + debug(`Patch lines: ${patchLines}`); + + return { + success: true, + patchPath: patchPath, + patchSize: patchSize, + patchLines: patchLines, + }; + } + + // No patch generated + return { + success: false, + error: errorMessage || "No changes to commit - no commits found", + patchPath: patchPath, + }; +} + /** * Handler for create_pull_request tool * Resolves the current branch if branch is not provided or is the base branch + * Generates git patch for the changes */ const createPullRequestHandler = args => { const entry = { ...args, type: "create_pull_request" }; @@ -497,12 +662,32 @@ const createPullRequestHandler = args => { entry.branch = detectedBranch; } + // Generate git patch + debug(`Generating patch for create_pull_request with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + + if (!patchResult.success) { + // Patch generation failed or patch is empty + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); + appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; @@ -511,6 +696,7 @@ const createPullRequestHandler = args => { /** * Handler for push_to_pull_request_branch tool * Resolves the current branch if branch is not provided or is the base branch + * Generates git patch for the changes */ const pushToPullRequestBranchHandler = args => { const entry = { ...args, type: "push_to_pull_request_branch" }; @@ -530,12 +716,32 @@ const pushToPullRequestBranchHandler = args => { entry.branch = detectedBranch; } + // Generate git patch + debug(`Generating patch for push_to_pull_request_branch with branch: ${entry.branch}`); + const patchResult = generateGitPatch(entry.branch); + + if (!patchResult.success) { + // Patch generation failed or patch is empty + const errorMsg = patchResult.error || "Failed to generate patch"; + debug(`Patch generation failed: ${errorMsg}`); + throw new Error(errorMsg); + } + + debug(`Patch generated successfully: ${patchResult.patchPath} (${patchResult.patchSize} bytes, ${patchResult.patchLines} lines)`); + appendSafeOutput(entry); return { content: [ { type: "text", - text: JSON.stringify({ result: "success" }), + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), }, ], }; diff --git a/pkg/workflow/js/safe_outputs_mcp_server_patch.test.cjs b/pkg/workflow/js/safe_outputs_mcp_server_patch.test.cjs new file mode 100644 index 000000000..49e93ff85 --- /dev/null +++ b/pkg/workflow/js/safe_outputs_mcp_server_patch.test.cjs @@ -0,0 +1,217 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import * as fs from "fs"; +import * as path from "path"; +import { execSync } from "child_process"; + +describe("safe_outputs_mcp_server.cjs - Patch Generation", () => { + describe("generateGitPatch function behavior", () => { + it("should detect when no changes are present", () => { + // Test the logic for detecting empty patches + const emptyPatchContent = ""; + const isEmpty = !emptyPatchContent || !emptyPatchContent.trim(); + + expect(isEmpty).toBe(true); + }); + + it("should detect when patch has content", () => { + // Test the logic for detecting non-empty patches + const patchWithContent = "From 1234567890abcdef\nSubject: Test commit\n\ndiff --git a/file.txt b/file.txt"; + const isEmpty = !patchWithContent || !patchWithContent.trim(); + + expect(isEmpty).toBe(false); + }); + + it("should calculate patch size correctly", () => { + // Test patch size calculation + const patchContent = "test content"; + const patchSize = Buffer.byteLength(patchContent, "utf8"); + + expect(patchSize).toBe(12); + }); + + it("should count patch lines correctly", () => { + // Test patch line counting + const patchContent = "line 1\nline 2\nline 3"; + const patchLines = patchContent.split("\n").length; + + expect(patchLines).toBe(3); + }); + + it("should handle empty patch result object", () => { + // Test the structure of an empty patch result + const emptyResult = { + success: false, + error: "No changes to commit - patch is empty", + patchPath: "/tmp/gh-aw/aw.patch", + patchSize: 0, + patchLines: 0, + }; + + expect(emptyResult.success).toBe(false); + expect(emptyResult.error).toContain("empty"); + expect(emptyResult.patchSize).toBe(0); + }); + + it("should handle successful patch result object", () => { + // Test the structure of a successful patch result + const successResult = { + success: true, + patchPath: "/tmp/gh-aw/aw.patch", + patchSize: 1024, + patchLines: 50, + }; + + expect(successResult.success).toBe(true); + expect(successResult.patchSize).toBeGreaterThan(0); + expect(successResult.patchLines).toBeGreaterThan(0); + }); + }); + + describe("handler error behavior", () => { + it("should throw error when patch generation fails", () => { + // Test error throwing behavior + const patchResult = { + success: false, + error: "No changes to commit - patch is empty", + }; + + expect(() => { + if (!patchResult.success) { + throw new Error(patchResult.error); + } + }).toThrow("No changes to commit - patch is empty"); + }); + + it("should not throw error when patch generation succeeds", () => { + // Test successful case doesn't throw + const patchResult = { + success: true, + patchPath: "/tmp/gh-aw/aw.patch", + patchSize: 1024, + patchLines: 50, + }; + + expect(() => { + if (!patchResult.success) { + throw new Error(patchResult.error || "Failed to generate patch"); + } + }).not.toThrow(); + }); + + it("should return success response with patch info", () => { + // Test successful response structure + const patchResult = { + success: true, + patchPath: "/tmp/gh-aw/aw.patch", + patchSize: 1024, + patchLines: 50, + }; + + const response = { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "success", + patch: { + path: patchResult.patchPath, + size: patchResult.patchSize, + lines: patchResult.patchLines, + }, + }), + }, + ], + }; + + expect(response.content).toHaveLength(1); + expect(response.content[0].type).toBe("text"); + + const responseData = JSON.parse(response.content[0].text); + expect(responseData.result).toBe("success"); + expect(responseData.patch.path).toBe("/tmp/gh-aw/aw.patch"); + expect(responseData.patch.size).toBe(1024); + expect(responseData.patch.lines).toBe(50); + }); + }); + + describe("git command patterns", () => { + it("should validate git branch name format", () => { + // Test branch name validation + const validBranchNames = ["main", "feature-123", "fix/bug-456", "develop"]; + const invalidBranchNames = ["", " ", " \n "]; + + validBranchNames.forEach(name => { + expect(name.trim()).not.toBe(""); + }); + + invalidBranchNames.forEach(name => { + expect(name.trim()).toBe(""); + }); + }); + + it("should validate patch path format", () => { + // Test patch path validation + const patchPath = "/tmp/gh-aw/aw.patch"; + + expect(patchPath).toMatch(/^\/tmp\/gh-aw\//); + expect(patchPath).toMatch(/\.patch$/); + expect(path.dirname(patchPath)).toBe("/tmp/gh-aw"); + expect(path.basename(patchPath)).toBe("aw.patch"); + }); + + it("should construct git format-patch command correctly", () => { + // Test command construction + const baseRef = "origin/main"; + const branchName = "feature-branch"; + const expectedCommand = `git format-patch ${baseRef}..${branchName} --stdout`; + + expect(expectedCommand).toContain("git format-patch"); + expect(expectedCommand).toContain(baseRef); + expect(expectedCommand).toContain(branchName); + expect(expectedCommand).toContain("--stdout"); + }); + + it("should construct git rev-list command correctly", () => { + // Test commit count command construction + const baseRef = "main"; + const headRef = "HEAD"; + const expectedCommand = `git rev-list --count ${baseRef}..${headRef}`; + + expect(expectedCommand).toContain("git rev-list"); + expect(expectedCommand).toContain("--count"); + expect(expectedCommand).toContain(baseRef); + expect(expectedCommand).toContain(headRef); + }); + }); + + describe("error messages", () => { + it("should provide clear error for empty patch", () => { + const error = "No changes to commit - patch is empty"; + + expect(error).toContain("No changes"); + expect(error).toContain("empty"); + }); + + it("should provide clear error for missing GITHUB_SHA", () => { + const error = "GITHUB_SHA environment variable is not set"; + + expect(error).toContain("GITHUB_SHA"); + expect(error).toContain("not set"); + }); + + it("should provide clear error for branch not found", () => { + const branchName = "feature-branch"; + const error = `Branch ${branchName} does not exist locally`; + + expect(error).toContain(branchName); + expect(error).toContain("does not exist"); + }); + + it("should provide clear error for general failure", () => { + const error = "Failed to generate patch: git command failed"; + + expect(error).toContain("Failed to generate patch"); + expect(error).toContain("git command failed"); + }); + }); +}); diff --git a/pkg/workflow/patch_generation_test.go b/pkg/workflow/patch_generation_test.go index 6878deeda..2fe8175b2 100644 --- a/pkg/workflow/patch_generation_test.go +++ b/pkg/workflow/patch_generation_test.go @@ -52,35 +52,34 @@ This workflow tests how patches are generated automatically. lockStr := string(lockContent) - // Check that git patch generation step is included in main job - if !strings.Contains(lockStr, "Generate git patch") { - t.Error("Expected 'Generate git patch' step in workflow") + // NOTE: Patch generation has been moved to the safe-outputs MCP server + // The patch is now generated when create_pull_request or push_to_pull_request_branch + // tools are called within the MCP server, not as a separate workflow step. + + // Check that the dedicated "Generate git patch" step is NOT in the main job anymore + if strings.Contains(lockStr, "Generate git patch") { + t.Error("Did not expect 'Generate git patch' step in main job (now handled by MCP server)") } - // Check that it uses git am to apply patches (newer approach) + // Check that patch application still happens in the create_pull_request job if !strings.Contains(lockStr, "git am /tmp/gh-aw/aw.patch") { - t.Error("Expected 'git am /tmp/gh-aw/aw.patch' command in git patch step") + t.Error("Expected 'git am /tmp/gh-aw/aw.patch' command in create_pull_request job") } - // Check that it pushes to origin branch + // Check that it pushes to origin branch in the create_pull_request job if !strings.Contains(lockStr, "git push origin") { - t.Error("Expected 'git push origin' command in git patch step") - } - - // Check that it generates patch from format-patch - if !strings.Contains(lockStr, "git format-patch") { - t.Error("Expected 'git format-patch' command in git patch step") + t.Error("Expected 'git push origin' command in create_pull_request job") } // Check that the create_pull_request job expects the patch file if !strings.Contains(lockStr, "No patch file found") { - t.Error("Expected pull request job to check for patch file existence") + t.Error("Expected create_pull_request job to check for patch file existence") } - // Verify the workflow has both main job and pull request job + // Verify the workflow has both main job and create_pull_request job if !strings.Contains(lockStr, "create_pull_request:") { t.Error("Expected create_pull_request job to be generated") } - t.Logf("Successfully verified patch generation workflow") + t.Logf("Successfully verified patch generation workflow (patch now generated in MCP server)") }