diff --git a/actions/setup/js/push_signed_commits.cjs b/actions/setup/js/push_signed_commits.cjs index 2546e0e9bd3..5b28dcfda41 100644 --- a/actions/setup/js/push_signed_commits.cjs +++ b/actions/setup/js/push_signed_commits.cjs @@ -128,7 +128,7 @@ async function readBlobAsBase64(blobHash, cwd) { * @param {string} opts.baseRef - Git ref of the remote head before commits were applied (used for rev-list) * @param {string} opts.cwd - Working directory of the local git checkout * @param {object} [opts.gitAuthEnv] - Environment variables for git push fallback auth - * @returns {Promise} + * @returns {Promise} SHA of the commit that landed on the target branch */ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, cwd, gitAuthEnv }) { // Collect the commits introduced (oldest-first) using topological order to ensure @@ -141,7 +141,7 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c if (shas.length === 0) { core.info("pushSignedCommits: no new commits to push via GraphQL"); - return; + return undefined; } core.info(`pushSignedCommits: replaying ${shas.length} commit(s) via GraphQL createCommitOnBranch (branch: ${branch}, repo: ${owner}/${repo})`); @@ -362,12 +362,16 @@ async function pushSignedCommits({ githubClient, owner, repo, branch, baseRef, c core.info(`pushSignedCommits: signed commit created: ${lastOid}`); } core.info(`pushSignedCommits: all ${shas.length} commit(s) pushed as signed commits`); + return lastOid ?? shas[shas.length - 1]; } catch (graphqlError) { core.warning(`pushSignedCommits: GraphQL signed push failed, falling back to git push: ${graphqlError instanceof Error ? graphqlError.message : String(graphqlError)}`); await exec.exec("git", ["push", "origin", branch], { cwd, env: { ...process.env, ...(gitAuthEnv || {}) }, }); + const fallbackSha = shas[shas.length - 1]; + core.info(`pushSignedCommits: git push fallback completed, using pushed SHA ${fallbackSha}`); + return fallbackSha; } } diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 2337566f7c4..ad3c52dcf39 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -555,6 +555,7 @@ async function main(config = {}) { // token on multi-commit branches where workflow files may have been modified). let newCommitCount = 0; let remoteHeadBeforePatch = ""; + let pushedCommitSha = ""; if (hasChanges) { // Capture HEAD before applying changes to compute new-commit count later try { @@ -736,7 +737,7 @@ async function main(config = {}) { // Push the applied commits to the branch using signed GraphQL commits (outside patch try/catch so push failures are not misattributed) try { - await pushSignedCommits({ + const pushedSha = await pushSignedCommits({ githubClient, owner: repoParts.owner, repo: repoParts.repo, @@ -745,6 +746,10 @@ async function main(config = {}) { cwd: process.cwd(), gitAuthEnv, }); + if (pushedSha) { + pushedCommitSha = pushedSha; + core.info(`pushSignedCommits returned pushed SHA: ${pushedSha}`); + } core.info(`Changes committed and pushed to branch: ${branchName}`); } catch (pushError) { const pushErrorMessage = getErrorMessage(pushError); @@ -856,12 +861,16 @@ async function main(config = {}) { } } - // Get commit SHA and push URL - const commitShaRes = await exec.getExecOutput("git", ["rev-parse", "HEAD"]); - if (commitShaRes.exitCode !== 0) { - return { success: false, error: "Failed to get commit SHA" }; + // The signed-push helper returns the commit SHA that landed on the branch. + // Fall back to local HEAD only if the helper did not return one. + let commitSha = pushedCommitSha; + if (!commitSha) { + const commitShaRes = await exec.getExecOutput("git", ["rev-parse", "HEAD"]); + if (commitShaRes.exitCode !== 0) { + return { success: false, error: "Failed to get commit SHA" }; + } + commitSha = commitShaRes.stdout.trim(); } - const commitSha = commitShaRes.stdout.trim(); // Get repository base URL and construct URLs // For cross-repo scenarios, use repoParts (the target repo) not context.repo (the workflow repo) diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index b7f94059c63..0a2bc25112b 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -637,6 +637,33 @@ index 0000000..abc1234 expect(result.commit_url).toContain("test-owner/test-repo/commit/"); }); + it("should use pushed commit SHA returned by pushSignedCommits for activation comment commit link", async () => { + const patchPath = createPatchFile(); + const updateActivationCommentModule = require("./update_activation_comment.cjs"); + const updateCommitSpy = vi.spyOn(updateActivationCommentModule, "updateActivationCommentWithCommit").mockResolvedValue(undefined); + const pushSignedCommitsModule = require("./push_signed_commits.cjs"); + const pushSignedSpy = vi.spyOn(pushSignedCommitsModule, "pushSignedCommits").mockResolvedValue("remote-head-after"); + + try { + mockExec.getExecOutput + .mockResolvedValueOnce({ exitCode: 0, stdout: "preflight-sha\trefs/heads/feature-branch\n", stderr: "" }) // preflight ls-remote + .mockResolvedValueOnce({ exitCode: 0, stdout: "local-head-before\n", stderr: "" }) // rev-parse HEAD before patch + .mockResolvedValueOnce({ exitCode: 0, stdout: "1\n", stderr: "" }); // rev-list --count + + const module = await loadModule(); + const handler = await module.main({}); + const result = await handler({ patch_path: patchPath }, {}); + + expect(result.success).toBe(true); + expect(result.commit_sha).toBe("remote-head-after"); + expect(result.commit_url).toContain("/commit/remote-head-after"); + expect(updateCommitSpy).toHaveBeenCalledWith(mockGithub, mockContext, mockCore, "remote-head-after", "https://github.com/test-owner/test-repo/commit/remote-head-after", { targetIssueNumber: 123 }); + } finally { + pushSignedSpy.mockRestore(); + updateCommitSpy.mockRestore(); + } + }); + it("should detect deleted branch before fetch", async () => { const patchPath = createPatchFile();