From 3851449f9750eeb5fe4d852bb1b326968e774b3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:13:53 +0000 Subject: [PATCH 1/2] Initial plan From 980c371759de7c8ccf7adc700d1ade92d8110b11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:42:06 +0000 Subject: [PATCH 2/2] fix: resolve temporary ID references in patch content before git am Temporary ID references (#aw_XXX) in patch content are now resolved before applying patches via `git am`. This fixes the issue where placeholders in committed source code (e.g., in URLs like `/issues/#aw_navqry1`) were left unresolved. The fix adds a new `replaceTemporaryIdReferencesInPatch()` function that handles both URL-context (removing the '#' prefix to avoid broken fragment anchors) and standard text-context replacement. Fixes: Temporary ID references in patch content are not resolved before `git am` Agent-Logs-Url: https://github.com/github/gh-aw/sessions/a5b77cd6-babe-476a-8e9e-a38a36011562 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/create_pull_request.cjs | 14 ++++++- actions/setup/js/temporary_id.cjs | 31 ++++++++++++++ actions/setup/js/temporary_id.test.cjs | 51 ++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index a01d62a9e72..46b50eba205 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -11,7 +11,7 @@ const { getTrackerID } = require("./get_tracker_id.cjs"); const { removeDuplicateTitleFromDescription } = require("./remove_duplicate_title.cjs"); const { sanitizeTitle, applyTitlePrefix } = require("./sanitize_title.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); -const { replaceTemporaryIdReferences, getOrGenerateTemporaryId } = require("./temporary_id.cjs"); +const { replaceTemporaryIdReferences, replaceTemporaryIdReferencesInPatch, getOrGenerateTemporaryId } = require("./temporary_id.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { addExpirationToFooter } = require("./ephemerals.cjs"); const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); @@ -991,6 +991,18 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${branchName} --repo // Apply the patch using git CLI (skip if empty) if (!isEmpty) { + // Resolve temporary ID references in patch content before applying + // This handles references like #aw_XXX in committed source code + if (resolvedTemporaryIds && Object.keys(resolvedTemporaryIds).length > 0) { + const tempIdMap = new Map(Object.entries(resolvedTemporaryIds)); + const originalPatchContent = patchContent; + patchContent = replaceTemporaryIdReferencesInPatch(patchContent, tempIdMap, itemRepo); + if (patchContent !== originalPatchContent) { + core.info("Resolved temporary ID references in patch content"); + fs.writeFileSync(patchFilePath, patchContent, "utf8"); + } + } + core.info("Applying patch..."); const patchLines = patchContent.split("\n"); const previewLineCount = Math.min(500, patchLines.length); diff --git a/actions/setup/js/temporary_id.cjs b/actions/setup/js/temporary_id.cjs index e72ee40d294..cb21069ad54 100644 --- a/actions/setup/js/temporary_id.cjs +++ b/actions/setup/js/temporary_id.cjs @@ -113,6 +113,36 @@ function replaceTemporaryIdReferences(text, tempIdMap, currentRepo) { }); } +/** + * Replace temporary ID references in patch content with actual issue numbers. + * Handles both URL-context and text-context replacements: + * - URL context: /issues/#aw_XXX → /issues/NUMBER (no '#' prefix, avoids broken fragment anchors) + * - Text context: #aw_XXX → #NUMBER (standard GitHub issue shorthand) + * + * @param {string} text - The patch content to process + * @param {Map} tempIdMap - Map of temporary_id to {repo, number} + * @param {string} [currentRepo] - Current repository slug for same-repo references + * @returns {string} Patch content with temporary IDs replaced + */ +function replaceTemporaryIdReferencesInPatch(text, tempIdMap, currentRepo) { + // First pass: URL-context replacement — //#aw_XXX → //NUMBER + // This must run before the standard replacement to avoid leaving a '#' in URLs + const urlContextPattern = /\/(#aw_[A-Za-z0-9_]{3,12})\b/gi; + let result = text.replace(urlContextPattern, (match, tempIdWithHash) => { + const tempId = tempIdWithHash.substring(1); // strip leading '#' + const resolved = tempIdMap.get(normalizeTemporaryId(tempId)); + if (resolved !== undefined) { + return `/${resolved.number}`; + } + return match; + }); + + // Second pass: standard text-context replacement — #aw_XXX → #NUMBER + result = replaceTemporaryIdReferences(result, tempIdMap, currentRepo); + + return result; +} + /** * Replace temporary ID references in text with actual issue numbers (legacy format) * This is a compatibility function that works with Map @@ -610,6 +640,7 @@ module.exports = { normalizeTemporaryId, getOrGenerateTemporaryId, replaceTemporaryIdReferences, + replaceTemporaryIdReferencesInPatch, replaceTemporaryIdReferencesLegacy, loadTemporaryIdMap, loadTemporaryIdMapFromResolved, diff --git a/actions/setup/js/temporary_id.test.cjs b/actions/setup/js/temporary_id.test.cjs index 045098957a5..243a49313e2 100644 --- a/actions/setup/js/temporary_id.test.cjs +++ b/actions/setup/js/temporary_id.test.cjs @@ -189,6 +189,57 @@ describe("temporary_id.cjs", () => { }); }); + describe("replaceTemporaryIdReferencesInPatch", () => { + it("should replace #aw_ID in text context within patch content", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_abc123", { repo: "owner/repo", number: 100 }]]); + const text = '+ [QuarantinedTest("#aw_abc123")]'; + expect(replaceTemporaryIdReferencesInPatch(text, map, "owner/repo")).toBe('+ [QuarantinedTest("#100")]'); + }); + + it("should replace #aw_ID in URL context without '#' prefix", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_navqry1", { repo: "dotnet/aspnetcore", number: 66195 }]]); + const text = '+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/#aw_navqry1")]'; + expect(replaceTemporaryIdReferencesInPatch(text, map, "dotnet/aspnetcore")).toBe('+ [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/66195")]'); + }); + + it("should handle mixed URL and text context references", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_issue1", { repo: "owner/repo", number: 42 }]]); + const text = "URL: https://github.com/owner/repo/issues/#aw_issue1 and ref #aw_issue1"; + expect(replaceTemporaryIdReferencesInPatch(text, map, "owner/repo")).toBe("URL: https://github.com/owner/repo/issues/42 and ref #42"); + }); + + it("should preserve unresolved references in patch content", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map(); + const text = "+ link: https://github.com/owner/repo/issues/#aw_unknown1"; + expect(replaceTemporaryIdReferencesInPatch(text, map, "owner/repo")).toBe("+ link: https://github.com/owner/repo/issues/#aw_unknown1"); + }); + + it("should handle cross-repo references in text context", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_abc123", { repo: "other/repo", number: 100 }]]); + const text = "See #aw_abc123 for details"; + expect(replaceTemporaryIdReferencesInPatch(text, map, "owner/repo")).toBe("See other/repo#100 for details"); + }); + + it("should be case-insensitive for URL context", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_abc123", { repo: "owner/repo", number: 100 }]]); + const text = "https://github.com/owner/repo/issues/#AW_ABC123"; + expect(replaceTemporaryIdReferencesInPatch(text, map, "owner/repo")).toBe("https://github.com/owner/repo/issues/100"); + }); + + it("should handle multiline patch content", async () => { + const { replaceTemporaryIdReferencesInPatch } = await import("./temporary_id.cjs"); + const map = new Map([["aw_navqry1", { repo: "dotnet/aspnetcore", number: 66195 }]]); + const text = ["diff --git a/test.cs b/test.cs", "--- a/test.cs", "+++ b/test.cs", "@@ -1,3 +1,4 @@", " existing line", '+[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/#aw_navqry1")]', " another line"].join("\n"); + expect(replaceTemporaryIdReferencesInPatch(text, map, "dotnet/aspnetcore")).toContain('QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/66195")'); + }); + }); + describe("getOrGenerateTemporaryId", () => { it("should auto-generate a temporary ID when not provided", async () => { const { getOrGenerateTemporaryId } = await import("./temporary_id.cjs");