From 953c5da95d91c9a98a4211387f4324476e76c8a5 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 3 Oct 2025 12:29:19 +0530 Subject: [PATCH 1/5] CHORE: Update PR Template to auto-close tasks --- .github/PULL_REQUEST_TEMPLATE.MD | 4 ++-- .gitignore | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD index f0244408e..886011883 100644 --- a/.github/PULL_REQUEST_TEMPLATE.MD +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -7,10 +7,10 @@ Only one reference is required - either GitHub issue OR ADO Work Item. --> -> AB# +> Closed AB# -> GitHub Issue: # +> Closes GitHub Issue: # ------------------------------------------------------------------- ### Summary diff --git a/.gitignore b/.gitignore index 095449ce0..0e9c43dab 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ build/ # Virtual environments *venv*/ **/*venv*/ + +# main.py - no need to track this file +main.py \ No newline at end of file From 96d1637fa7c6c648ca5c07b80b50eb0f4ffce57c Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 3 Oct 2025 12:34:18 +0530 Subject: [PATCH 2/5] CHORE: Update PR Template to auto-close tasks --- .github/PULL_REQUEST_TEMPLATE.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD index 886011883..452051717 100644 --- a/.github/PULL_REQUEST_TEMPLATE.MD +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -7,10 +7,10 @@ Only one reference is required - either GitHub issue OR ADO Work Item. --> -> Closed AB# +> ADO Work Item: Closed AB# -> Closes GitHub Issue: # +> GitHub Issue: Closes # ------------------------------------------------------------------- ### Summary From 7001e7ea9c01264e51a28b91c027d958e364b1e6 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Thu, 9 Oct 2025 14:45:44 +0530 Subject: [PATCH 3/5] Changed ADO Fixes --- .github/PULL_REQUEST_TEMPLATE.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD index 452051717..1644deea9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.MD +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -7,7 +7,7 @@ Only one reference is required - either GitHub issue OR ADO Work Item. --> -> ADO Work Item: Closed AB# +> ADO Work Item: Fixed AB# > GitHub Issue: Closes # @@ -52,4 +52,4 @@ mssql-python maintainers: - Create an ADO Work Item following internal processes - Link the ADO Work Item in the "ADO Work Item" section above - Follow the PR title format and provide a meaningful summary ---> \ No newline at end of file +--> From 1e8fcaf88ba6e75130ede2e518e5d14afac9adbf Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 14:54:26 +0530 Subject: [PATCH 4/5] CHORE: Add workflow to notify linked issues on PR merge When a PR closes one or more GitHub issues and is merged into main, post a release-cycle heads-up comment on each linked issue. Closing remains driven by GitHub's native auto-close keywords in the PR template; this workflow only comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pr-merge-issue-notify.yml | 141 ++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .github/workflows/pr-merge-issue-notify.yml diff --git a/.github/workflows/pr-merge-issue-notify.yml b/.github/workflows/pr-merge-issue-notify.yml new file mode 100644 index 000000000..633bcc32f --- /dev/null +++ b/.github/workflows/pr-merge-issue-notify.yml @@ -0,0 +1,141 @@ +name: Notify Linked Issues on PR Merge + +# When a PR that closes one or more GitHub issues is merged into `main`, +# post a release-cycle heads-up comment on each linked issue. +# +# - Closing of the issue is left to GitHub's native auto-close (driven by +# the `Closes #N` keyword in the PR template). This workflow does NOT +# close anything itself; it only adds a comment. +# - Linked issues are discovered via the PR's `closingIssuesReferences` +# GraphQL connection, which covers both keyword references in the body +# AND issues linked via the "Development" sidebar. + +# Uses `pull_request_target` (not `pull_request`) so that PRs originating +# from forks still run with a write-capable `GITHUB_TOKEN`. This is safe +# here because the workflow never checks out PR code — it only reads the +# event payload and calls GitHub REST/GraphQL APIs. +on: + pull_request_target: + types: [closed] + branches: [main] + +permissions: + contents: read + +jobs: + notify-linked-issues: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: read + steps: + - name: Comment on linked issues + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = context.payload.pull_request.number; + const prTitle = context.payload.pull_request.title; + const prUrl = context.payload.pull_request.html_url; + const baseRef = context.payload.pull_request.base.ref; + + // Sentinel so re-runs of this workflow don't double-comment. + const SENTINEL = ''; + + // Fetch the issues this PR closes via the GraphQL + // `closingIssuesReferences` connection. Paginate defensively + // even though most PRs link only one issue. + const linked = []; + let cursor = null; + // eslint-disable-next-line no-constant-condition + while (true) { + const result = await github.graphql( + `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $pr) { + closingIssuesReferences(first: 50, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { number repository { owner { login } name } } + } + } + } + }`, + { + owner: context.repo.owner, + repo: context.repo.repo, + pr: prNumber, + cursor, + } + ); + const conn = result.repository.pullRequest.closingIssuesReferences; + for (const node of conn.nodes) { + // Only comment on issues in this same repo. Cross-repo + // links are unusual here and would need different perms. + if ( + node.repository.owner.login === context.repo.owner && + node.repository.name === context.repo.repo + ) { + linked.push(node.number); + } + } + if (!conn.pageInfo.hasNextPage) break; + cursor = conn.pageInfo.endCursor; + } + + if (linked.length === 0) { + core.info(`PR #${prNumber} has no linked closing issues; nothing to do.`); + return; + } + + core.info(`Linked issues for PR #${prNumber}: ${linked.join(', ')}`); + + const failures = []; + + const body = + `${SENTINEL}\n` + + `🚀 The fix from #${prNumber} ` + + `(_${prTitle}_) has been merged into \`${baseRef}\` ` + + `and will ship in the next mssql-python release.\n\n` + + `Thanks for reporting — watch [Releases](https://github.com/${context.repo.owner}/${context.repo.repo}/releases) for the announcement.`; + + for (const issueNumber of linked) { + try { + // Idempotency: skip if we (or a prior run) already left the + // sentinel comment on this issue. + const existing = await github.paginate( + github.rest.issues.listComments, + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + per_page: 100, + } + ); + const already = existing.some( + (c) => c.body && c.body.includes(SENTINEL) && c.body.includes(`#${prNumber}`) + ); + if (already) { + core.info(`Issue #${issueNumber}: notify comment already present, skipping.`); + continue; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body, + }); + core.info(`Issue #${issueNumber}: posted release-cycle comment.`); + } catch (err) { + const msg = `Issue #${issueNumber}: failed to comment — ${err.message}`; + core.error(msg); + failures.push(msg); + } + } + + if (failures.length > 0) { + core.setFailed( + `Failed to notify ${failures.length} of ${linked.length} linked issue(s). See logs above.` + ); + } From a9b32357b19cc1a3c600ec9994d973df9d634b5a Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 15:04:45 +0530 Subject: [PATCH 5/5] CHORE: GH issues comment-only on merge; ADO retains native auto-close Per the design discussion on PR #275: GitHub issues should NOT auto-close on PR merge. Maintainers close them manually after the fix actually ships in a release. ADO work items, in contrast, still auto-close immediately via the native 'Fixed AB#' keyword in the PR template. - Template: drop 'Closes' from the GitHub Issue line so GitHub no longer auto-closes the issue at merge time. ADO line keeps 'Fixed AB#' unchanged. - Workflow: switch from GraphQL closingIssuesReferences (which goes empty without the Closes keyword) to a body-parser that extracts issue numbers from the template's 'GitHub Issue: #' line. Tolerates legacy 'Closes/Fixes/Resolves' wording and comma-separated lists. Comments never closes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.MD | 2 +- .github/workflows/pr-merge-issue-notify.yml | 128 +++++++++++--------- 2 files changed, 69 insertions(+), 61 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD index 1644deea9..3ee32a401 100644 --- a/.github/PULL_REQUEST_TEMPLATE.MD +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -10,7 +10,7 @@ Only one reference is required - either GitHub issue OR ADO Work Item. > ADO Work Item: Fixed AB# -> GitHub Issue: Closes # +> GitHub Issue: # ------------------------------------------------------------------- ### Summary diff --git a/.github/workflows/pr-merge-issue-notify.yml b/.github/workflows/pr-merge-issue-notify.yml index 633bcc32f..89af10a28 100644 --- a/.github/workflows/pr-merge-issue-notify.yml +++ b/.github/workflows/pr-merge-issue-notify.yml @@ -1,19 +1,20 @@ -name: Notify Linked Issues on PR Merge +name: Notify Linked GitHub Issues on PR Merge -# When a PR that closes one or more GitHub issues is merged into `main`, -# post a release-cycle heads-up comment on each linked issue. +# When a PR is merged into `main`, post a release-cycle heads-up comment +# on each GitHub issue referenced in the PR body via the template line: # -# - Closing of the issue is left to GitHub's native auto-close (driven by -# the `Closes #N` keyword in the PR template). This workflow does NOT -# close anything itself; it only adds a comment. -# - Linked issues are discovered via the PR's `closingIssuesReferences` -# GraphQL connection, which covers both keyword references in the body -# AND issues linked via the "Development" sidebar. +# > GitHub Issue: # +# +# We deliberately do NOT close the GitHub issue here — maintainers close +# GH issues manually once the fix actually ships in a release. ADO work +# items, in contrast, are still auto-closed on merge via the native +# `Fixed AB#` keyword in the PR template (handled by ADO, not this +# workflow). +# +# Uses `pull_request_target` so the token has `issues: write` even for +# PRs that originate from forks. Safe here: the workflow never checks +# out PR code — it only reads the event payload and calls GitHub APIs. -# Uses `pull_request_target` (not `pull_request`) so that PRs originating -# from forks still run with a write-capable `GITHUB_TOKEN`. This is safe -# here because the workflow never checks out PR code — it only reads the -# event payload and calls GitHub REST/GraphQL APIs. on: pull_request_target: types: [closed] @@ -37,72 +38,74 @@ jobs: script: | const prNumber = context.payload.pull_request.number; const prTitle = context.payload.pull_request.title; - const prUrl = context.payload.pull_request.html_url; + const prBody = context.payload.pull_request.body || ''; const baseRef = context.payload.pull_request.base.ref; // Sentinel so re-runs of this workflow don't double-comment. const SENTINEL = ''; - // Fetch the issues this PR closes via the GraphQL - // `closingIssuesReferences` connection. Paginate defensively - // even though most PRs link only one issue. - const linked = []; - let cursor = null; - // eslint-disable-next-line no-constant-condition - while (true) { - const result = await github.graphql( - `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $pr) { - closingIssuesReferences(first: 50, after: $cursor) { - pageInfo { hasNextPage endCursor } - nodes { number repository { owner { login } name } } - } - } - } - }`, - { - owner: context.repo.owner, - repo: context.repo.repo, - pr: prNumber, - cursor, - } - ); - const conn = result.repository.pullRequest.closingIssuesReferences; - for (const node of conn.nodes) { - // Only comment on issues in this same repo. Cross-repo - // links are unusual here and would need different perms. - if ( - node.repository.owner.login === context.repo.owner && - node.repository.name === context.repo.repo - ) { - linked.push(node.number); - } + // Parse issue numbers from the PR template's + // > GitHub Issue: # + // line. Anchored to the template wording so unrelated `#123` + // mentions elsewhere in the body don't get spammed. + // + // Tolerated variants: + // GitHub Issue: #149 + // GitHub Issue: Closes #149 (legacy template format) + // GitHub Issue: Fixes #149, #150 + // github issue:#149 + const issueRefRegex = + /github\s*issue\s*:\s*(?:(?:closes|closed|fixes|fixed|resolves|resolved)\s+)?((?:#\d+(?:\s*,\s*#\d+)*))/gi; + const numbers = new Set(); + let match; + while ((match = issueRefRegex.exec(prBody)) !== null) { + for (const m of match[1].matchAll(/#(\d+)/g)) { + numbers.add(Number(m[1])); } - if (!conn.pageInfo.hasNextPage) break; - cursor = conn.pageInfo.endCursor; } + // Don't ever comment on the PR itself (paranoia: PR numbers + // and issue numbers share the same namespace). + numbers.delete(prNumber); + + const linked = [...numbers]; if (linked.length === 0) { - core.info(`PR #${prNumber} has no linked closing issues; nothing to do.`); + core.info( + `PR #${prNumber}: no GitHub issue references found in body; nothing to do.` + ); return; } - core.info(`Linked issues for PR #${prNumber}: ${linked.join(', ')}`); - - const failures = []; + core.info(`PR #${prNumber} references issues: ${linked.join(', ')}`); const body = `${SENTINEL}\n` + `🚀 The fix from #${prNumber} ` + `(_${prTitle}_) has been merged into \`${baseRef}\` ` + `and will ship in the next mssql-python release.\n\n` + - `Thanks for reporting — watch [Releases](https://github.com/${context.repo.owner}/${context.repo.repo}/releases) for the announcement.`; + `This issue will be closed once the release is published — ` + + `track [Releases](https://github.com/${context.repo.owner}/${context.repo.repo}/releases) for the announcement. ` + + `Thanks for reporting!`; + + const failures = []; for (const issueNumber of linked) { try { - // Idempotency: skip if we (or a prior run) already left the - // sentinel comment on this issue. + // Sanity: confirm the target is actually an Issue (not a PR). + // GitHub treats both as "issues" in the REST API; we want to + // skip if the reference happens to be a PR number. + const issue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + }); + if (issue.data.pull_request) { + core.info(`#${issueNumber} is a PR, not an issue — skipping.`); + continue; + } + + // Idempotency: skip if a previous run already left the + // sentinel comment for this PR on this issue. const existing = await github.paginate( github.rest.issues.listComments, { @@ -113,10 +116,15 @@ jobs: } ); const already = existing.some( - (c) => c.body && c.body.includes(SENTINEL) && c.body.includes(`#${prNumber}`) + (c) => + c.body && + c.body.includes(SENTINEL) && + c.body.includes(`#${prNumber}`) ); if (already) { - core.info(`Issue #${issueNumber}: notify comment already present, skipping.`); + core.info( + `Issue #${issueNumber}: notify comment already present, skipping.` + ); continue; }