@@ -211,6 +211,14 @@ export async function getNewChangeEntries({
211211 useChangelogEntry ,
212212 ) ;
213213
214+ // Pre-build a Set of descriptions from PR-based commits for efficient lookup
215+ // This avoids O(N*M) complexity when checking for corresponding merge commits
216+ const prCommitDescriptions = new Set (
217+ commits
218+ . filter ( ( commit ) => commit . prNumber )
219+ . map ( ( commit ) => commit . description . trim ( ) ) ,
220+ ) ;
221+
214222 // Filter commits to exclude duplicates:
215223 // - For commits with PR numbers: check if PR number already exists in changelog
216224 // - For commits without PR numbers: check if the description already exists in changelog
@@ -219,24 +227,20 @@ export async function getNewChangeEntries({
219227 // PR-based commit: check if this PR number is already logged
220228 return ! loggedPrNumbers . includes ( prNumber ) ;
221229 }
222-
230+
223231 // Direct commit (no PR number): need to handle two cases
224-
232+
225233 // Case 1: Check if there's a corresponding merge commit in this batch
226234 // This handles squash merges where both the original commit and merge commit appear
227- const hasCorrespondingMergeCommit = commits . some (
228- ( commit ) =>
229- commit . prNumber && commit . description . trim ( ) === description . trim ( ) ,
230- ) ;
231-
232- if ( hasCorrespondingMergeCommit ) {
235+ const normalizedDescription = description . trim ( ) ;
236+ if ( prCommitDescriptions . has ( normalizedDescription ) ) {
233237 // Skip this commit - the merge commit with PR number will be used instead
234238 return false ;
235239 }
236-
240+
237241 // Case 2: Check if this exact description is already logged in the changelog
238242 // Trim description to match pre-normalized loggedDescriptions
239- return ! loggedDescriptions . includes ( description . trim ( ) ) ;
243+ return ! loggedDescriptions . includes ( normalizedDescription ) ;
240244 } ) ;
241245
242246 return newCommits . map ( ( { prNumber, subject, description } ) => {
0 commit comments