Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion actions/setup/js/create_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions actions/setup/js/temporary_id.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, RepoIssuePair>} 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 — /<path>/#aw_XXX → /<path>/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<string, number>
Expand Down Expand Up @@ -610,6 +640,7 @@ module.exports = {
normalizeTemporaryId,
getOrGenerateTemporaryId,
replaceTemporaryIdReferences,
replaceTemporaryIdReferencesInPatch,
replaceTemporaryIdReferencesLegacy,
loadTemporaryIdMap,
loadTemporaryIdMapFromResolved,
Expand Down
51 changes: 51 additions & 0 deletions actions/setup/js/temporary_id.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading