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
218 changes: 218 additions & 0 deletions .github/workflows/autolabel-pr-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: Auto Label PR from Linked Issue

on:
pull_request_target:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: write
issues: write
contents: read

jobs:
label-pr:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Extract Issue Numbers from PR Body
id: extract-issues
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
let prNumber, prBody, prTitle;

// Check if triggered by issue event
if (context.eventName === 'issues') {
// Find all open PRs that link to this issue
const issueNumber = context.payload.issue.number;
console.log(`Issue #${issueNumber} labels were updated`);

// Search for PRs that mention this issue
const { data: pullRequests } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
});

const linkedPRs = [];
for (const pr of pullRequests) {
const prText = `${pr.title} ${pr.body || ''}`;
const patterns = [
new RegExp(`(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s+#${issueNumber}\\b`, 'i'),
new RegExp(`#${issueNumber}\\b`)
];

if (patterns.some(p => p.test(prText))) {
linkedPRs.push(pr.number);
}
}

if (linkedPRs.length === 0) {
console.log('No linked PRs found for this issue');
return JSON.stringify({ prs: [], issue: issueNumber });
}

console.log(`Found linked PRs: ${linkedPRs.join(', ')}`);
return JSON.stringify({ prs: linkedPRs, issue: issueNumber });
} else {
// Triggered by PR event - original logic
prBody = context.payload.pull_request.body || '';
prTitle = context.payload.pull_request.title || '';

const patterns = [
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi,
/#(\d+)/g
];

const issueNumbers = new Set();
const textToSearch = prBody + ' ' + prTitle;

patterns.forEach(pattern => {
const matches = [...textToSearch.matchAll(pattern)];
matches.forEach(match => {
issueNumbers.add(match[1]);
});
});

const issues = Array.from(issueNumbers);
console.log('Found linked issues:', issues);

return JSON.stringify({
prs: [context.payload.pull_request.number],
issues: issues
});
}

- name: Get Labels from Linked Issues
id: get-labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const extractData = JSON.parse('${{ steps.extract-issues.outputs.result }}');

// Labels to exclude from being applied to PRs
const excludedLabels = ['recode', 'hacktoberfest-accepted'];

let issueNumbers = [];
let prsToUpdate = [];

// Handle both PR and issue events
if (extractData.issue) {
// Issue event - update all linked PRs
issueNumbers = [extractData.issue];
prsToUpdate = extractData.prs || [];
} else {
// PR event - update the current PR
issueNumbers = extractData.issues || [];
prsToUpdate = extractData.prs || [];
}

if (!issueNumbers || issueNumbers.length === 0) {
console.log('No linked issues found');
return JSON.stringify({ labels: [], prs: prsToUpdate });
}

const allLabels = new Set();

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

console.log(`Issue #${issueNumber} labels:`, issue.data.labels.map(l => l.name));

issue.data.labels.forEach(label => {
// Only add label if it's not in the excluded list
if (!excludedLabels.includes(label.name.toLowerCase())) {
allLabels.add(label.name);
} else {
console.log(`Excluding label: ${label.name}`);
}
});
} catch (error) {
console.log(`Could not fetch issue #${issueNumber}:`, error.message);
}
}

const labels = Array.from(allLabels);
console.log('All labels to apply:', labels);

return JSON.stringify({ labels: labels, prs: prsToUpdate });

- name: Apply Labels to PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const data = JSON.parse('${{ steps.get-labels.outputs.result }}');
const labels = data.labels || [];
const prsToUpdate = data.prs || [];

if (!labels || labels.length === 0) {
console.log('No labels to apply');
return;
}

if (!prsToUpdate || prsToUpdate.length === 0) {
console.log('No PRs to update');
return;
}

// Update each PR
for (const prNumber of prsToUpdate) {
try {
// First, get current PR labels
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});

// Remove all existing labels first (to handle removed issue labels)
const currentLabels = pr.labels.map(l => l.name);
if (currentLabels.length > 0) {
for (const label of currentLabels) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label
});
} catch (e) {
console.log(`Could not remove label ${label}: ${e.message}`);
}
}
}

// Apply new labels
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: labels
});

console.log(`Successfully applied ${labels.length} labels to PR #${prNumber}`);

// Add a comment to the PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `🏷️ Labels automatically synced from linked issue(s): ${labels.map(l => `\`${l}\``).join(', ')}`
});
} catch (error) {
console.error(`Error updating PR #${prNumber}:`, error.message);
}
}
4 changes: 2 additions & 2 deletions .github/workflows/autolabler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
await github.rest.issues.addLabels({
...context.repo,
issue_number: prNumber,
labels: ["recode", "level 1", "hacktoberfest-accepted"]
labels: ["recode","hacktoberfest-accepted"]
});
console.log(`Added labels [recode, level 1, hacktoberfest-accepted] to PR #${prNumber}`);
console.log(`Added labels [recode, hacktoberfest-accepted] to PR #${prNumber}`);
- name: Add labels to Issue
if: github.event_name == 'issues'
Expand Down
Loading