From 22e379bd7eaa4786be543423a81f97f0a9ea07a6 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Thu, 14 May 2026 05:45:26 -0700 Subject: [PATCH] fix(mirror): reconcile stale solved issue links Clear solved_by_pr links that still point to a refreshed PR when its closing references change or it is no longer merged, then reapply current merged links. Fixes #59 --- packages/das/src/queue/fetch.processor.ts | 71 +++++++++++++++++++---- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/packages/das/src/queue/fetch.processor.ts b/packages/das/src/queue/fetch.processor.ts index 27860d2..6b4e4dd 100644 --- a/packages/das/src/queue/fetch.processor.ts +++ b/packages/das/src/queue/fetch.processor.ts @@ -1,7 +1,7 @@ import { Processor, WorkerHost, InjectQueue } from "@nestjs/bullmq"; import { Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { IsNull, Repository } from "typeorm"; +import { In, IsNull, Repository } from "typeorm"; import { Job, Queue } from "bullmq"; import { Issue, PullRequest } from "../entities"; import { GitHubFetcherService } from "../webhook/github-fetcher.service"; @@ -79,28 +79,33 @@ export class FetchProcessor extends WorkerHost { ): Promise { this.logger.log(`Fetching PR metadata for ${repoFullName}#${prNumber}`); + const previousPr = await this.prRepo.findOneBy({ repoFullName, prNumber }); + const previousClosingIssueNumbers = this.uniqueIssueNumbers( + previousPr?.closingIssueNumbers ?? [], + ); + const { closingIssueNumbers, body, lastEditedAt } = await this.fetcher.fetchPrMetadata(repoFullName, prNumber); + const currentClosingIssueNumbers = + this.uniqueIssueNumbers(closingIssueNumbers); await this.prRepo.update( { repoFullName, prNumber }, { - closingIssueNumbers, + closingIssueNumbers: currentClosingIssueNumbers, body, lastEditedAt, }, ); - // If this PR is merged, mark each linked issue as solved_by_pr const pr = await this.prRepo.findOneBy({ repoFullName, prNumber }); - if (pr?.state === "MERGED" && closingIssueNumbers.length > 0) { - for (const issueNumber of closingIssueNumbers) { - await this.issueRepo.update( - { repoFullName, issueNumber }, - { solvedByPr: prNumber }, - ); - } - } + await this.reconcileSolvedIssueLinks( + repoFullName, + prNumber, + previousClosingIssueNumbers, + currentClosingIssueNumbers, + pr?.state === "MERGED", + ); } private async handlePrFiles(data: PrFilesJobData): Promise { @@ -222,4 +227,48 @@ export class FetchProcessor extends WorkerHost { baseSha: generation.baseSha ?? IsNull(), }; } + + private async reconcileSolvedIssueLinks( + repoFullName: string, + prNumber: number, + previousIssueNumbers: number[], + currentIssueNumbers: number[], + isMerged: boolean, + ): Promise { + const currentIssueNumberSet = new Set(currentIssueNumbers); + const staleIssueNumbers = previousIssueNumbers.filter( + (issueNumber) => !currentIssueNumberSet.has(issueNumber), + ); + + const clearIssueNumbers = isMerged + ? staleIssueNumbers + : this.uniqueIssueNumbers([ + ...previousIssueNumbers, + ...currentIssueNumbers, + ]); + + if (clearIssueNumbers.length > 0) { + await this.issueRepo.update( + { + repoFullName, + issueNumber: In(clearIssueNumbers), + solvedByPr: prNumber, + }, + { solvedByPr: null }, + ); + } + + if (!isMerged || currentIssueNumbers.length === 0) { + return; + } + + await this.issueRepo.update( + { repoFullName, issueNumber: In(currentIssueNumbers) }, + { solvedByPr: prNumber }, + ); + } + + private uniqueIssueNumbers(issueNumbers: number[]): number[] { + return [...new Set(issueNumbers)]; + } }