Skip to content
Merged
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
93 changes: 93 additions & 0 deletions .github/workflows/pr-issue-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Sync PR data from Linked Issues

on:
pull_request:
types: [opened, edited, synchronize]

jobs:
sync-metadata:
runs-on: ubuntu-latest

permissions:
issues: write
pull-requests: write

steps:
- name: Extract Linked Issues
id: extract
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || "";
const issuePattern = /#(\d+)/g;
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern will match any '#' followed by digits, potentially capturing false positives like '#123' in code snippets or unrelated contexts. Consider using a more specific pattern that looks for common issue linking phrases like 'fixes #123', 'closes #123', or 'resolves #123'.

Suggested change
const issuePattern = /#(\d+)/g;
const issuePattern = /\b(?:fixe?s|close[sd]?|resolve[sd]?)[:\s]+#(\d+)/gi;

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /#(\d+)/g will match any #number format, but GitHub's issue linking requires specific keywords like 'fixes', 'closes', 'resolves' followed by the issue number. This could incorrectly match unrelated hash numbers in the PR body.

Suggested change
const issuePattern = /#(\d+)/g;
// Match "fixes #123", "closes: #456", "resolves #789", etc. (case-insensitive)
const issuePattern = /\b(?:fixe?[sd]?|close[sd]?|resolve[sd]?)\s*[: ]+\s*#(\d+)/gi;

Copilot uses AI. Check for mistakes.

let matches = [];
let m;
while ((m = issuePattern.exec(body)) !== null) {
matches.push(parseInt(m[1]));
}
core.setOutput("issues", JSON.stringify(matches));

- name: Post or Update data Comment
if: steps.extract.outputs.issues && steps.extract.outputs.issues != '[]'
uses: actions/github-script@v7
with:
script: |
const issuesInput = core.getInput("issues") || "[]";
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script tries to get input 'issues' but it should use the output from the previous step. Use steps.extract.outputs.issues instead of core.getInput('issues').

Suggested change
const issuesInput = core.getInput("issues") || "[]";
const issuesInput = process.env.ISSUES || "[]";

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core.getInput("issues") call will not retrieve the output from the previous step. It should be steps.extract.outputs.issues to access the output from the extract step.

Suggested change
const issuesInput = core.getInput("issues") || "[]";
const issuesInput = steps.extract.outputs.issues || "[]";

Copilot uses AI. Check for mistakes.

const issues = JSON.parse(issuesInput);
const prNumber = context.payload.pull_request.number;

let combinedLabels = [];
let combinedAssignees = [];
let combinedMilestones = [];

for (const number of issues) {
try {
const issue = await github.rest.issues.get({
...context.repo,
issue_number: number
});

combinedLabels.push(...issue.data.labels.map(l => l.name));
combinedAssignees.push(...issue.data.assignees.map(a => a.login));
if (issue.data.milestone) combinedMilestones.push(issue.data.milestone.title);

} catch (err) {
console.log(`Could not fetch issue #${number}: ${err.message}`);
}
}

// Deduplicate
combinedLabels = [...new Set(combinedLabels)];
combinedAssignees = [...new Set(combinedAssignees)];
combinedMilestones = [...new Set(combinedMilestones)];

const commentBody =
`### Synced data from Linked Issues\n\n` +
`**Labels:**\n${combinedLabels.length ? combinedLabels.map(l => `- ${l}`).join("\n") : "- None"}\n\n` +
`**Assignees:**\n${combinedAssignees.length ? combinedAssignees.map(a => `- ${a}`).join("\n") : "- None"}\n\n` +
`**Milestones:**\n${combinedMilestones.length ? combinedMilestones.map(m => `- ${m}`).join("\n") : "- None"}\n`;

// Get existing comments
const comments = await github.rest.issues.listComments({
...context.repo,
issue_number: prNumber
});

// Find existing workflow comment
const existingComment = comments.data.find(c => c.body.includes("### Synced data from Linked Issues"));

if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
...context.repo,
comment_id: existingComment.id,
body: commentBody
});
} else {
// Create new comment
await github.rest.issues.createComment({
...context.repo,
issue_number: prNumber,
body: commentBody
});
}
Loading