@@ -37,7 +37,7 @@ function allIncluded(outputTarget = 'email') {
37
37
let issue_opened_button =
38
38
'<div style="vertical-align:middle;display: inline-block;padding: 0px 4px;font-size:9px;font-weight: 600;color: #fff;text-align: center;background-color: #2cbe4e;border-radius: 3px;line-height: 12px;margin-bottom: 2px;" class="State State--green">open</div>' ;
39
39
40
- const DEBUG = false ; // Set to false to disable debug logs
40
+ const DEBUG = true ; // Set to false to disable debug logs
41
41
42
42
function log ( ...args ) {
43
43
if ( DEBUG ) {
@@ -319,34 +319,103 @@ function allIncluded(outputTarget = 'email') {
319
319
const githubUserData = await userRes . json ( ) ;
320
320
const allPrsData = await allPrsRes . json ( ) ;
321
321
322
- // Fetch commits for each PR
323
- const prCommits = await Promise . all ( allPrsData . items . map ( async pr => {
324
- const repository_url = pr . repository_url ;
325
- const [ owner , project ] = repository_url . split ( '/' ) . slice ( - 2 ) ;
326
- const commitsUrl = `https://api.github.com/repos/${ owner } /${ project } /pulls/${ pr . number } /commits` ;
327
-
328
- try {
329
- await new Promise ( res => setTimeout ( res , 100 ) ) ; // Small delay to avoid rate limits
330
- const commitsRes = await fetch ( commitsUrl ) ;
331
- if ( ! commitsRes . ok ) return null ;
332
- const commits = await commitsRes . json ( ) ;
322
+ // Filter to only get open PRs
323
+ const openPrs = allPrsData . items . filter ( pr => pr . state === 'open' ) ;
324
+ log ( `Found ${ openPrs . length } open PRs out of ${ allPrsData . items . length } total PRs` ) ;
325
+
326
+ // Fetch commits for each open PR with better rate limiting and caching
327
+ const fetchCommitsWithRetry = async ( owner , project , pr_number , retryCount = 3 ) => {
328
+ const commitsUrl = `https://api.github.com/repos/${ owner } /${ project } /pulls/${ pr_number } /commits` ;
329
+
330
+ // Check if we have an error cached for this URL
331
+ const errorKey = `${ owner } /${ project } /${ pr_number } ` ;
332
+ const cachedError = githubCache . errors [ errorKey ] ;
333
+ if ( cachedError && ( Date . now ( ) - cachedError . timestamp ) < githubCache . errorTTL ) {
334
+ log ( `Skipping ${ errorKey } due to recent error` ) ;
335
+ return null ;
336
+ }
337
+
338
+ for ( let i = 0 ; i < retryCount ; i ++ ) {
339
+ try {
340
+ // Add exponential backoff between retries
341
+ if ( i > 0 ) {
342
+ await new Promise ( res => setTimeout ( res , Math . pow ( 2 , i ) * 1000 ) ) ;
343
+ }
344
+
345
+ const commitsRes = await fetch ( commitsUrl ) ;
346
+ if ( commitsRes . status === 404 ) {
347
+ // Cache 404 errors to avoid retrying
348
+ githubCache . errors [ errorKey ] = { timestamp : Date . now ( ) , status : 404 } ;
349
+ return null ;
350
+ }
351
+
352
+ if ( commitsRes . status === 403 ) {
353
+ // Rate limit hit - wait longer
354
+ const resetTime = commitsRes . headers . get ( 'X-RateLimit-Reset' ) ;
355
+ if ( resetTime ) {
356
+ const waitTime = ( parseInt ( resetTime ) * 1000 ) - Date . now ( ) ;
357
+ if ( waitTime > 0 ) {
358
+ await new Promise ( res => setTimeout ( res , waitTime ) ) ;
359
+ }
360
+ }
361
+ continue ;
362
+ }
363
+
364
+ if ( ! commitsRes . ok ) {
365
+ throw new Error ( `HTTP ${ commitsRes . status } ` ) ;
366
+ }
367
+
368
+ const commits = await commitsRes . json ( ) ;
369
+ log ( `Fetched ${ commits . length } commits for PR #${ pr_number } in ${ owner } /${ project } ` ) ;
370
+ return commits ;
371
+
372
+ } catch ( err ) {
373
+ if ( i === retryCount - 1 ) {
374
+ // Cache error on final retry
375
+ githubCache . errors [ errorKey ] = { timestamp : Date . now ( ) , error : err . message } ;
376
+ logError ( `Failed to fetch commits for PR #${ pr_number } after ${ retryCount } retries:` , err ) ;
377
+ return null ;
378
+ }
379
+ }
380
+ }
381
+ return null ;
382
+ } ;
383
+
384
+ // Process PRs in batches to avoid rate limiting
385
+ const batchSize = 3 ;
386
+ const prCommits = [ ] ;
387
+
388
+ for ( let i = 0 ; i < openPrs . length ; i += batchSize ) {
389
+ const batch = openPrs . slice ( i , i + batchSize ) ;
390
+ const batchResults = await Promise . all ( batch . map ( async pr => {
391
+ const repository_url = pr . repository_url ;
392
+ const [ owner , project ] = repository_url . split ( '/' ) . slice ( - 2 ) ;
393
+
394
+ // Add delay between PR commit fetches
395
+ await new Promise ( res => setTimeout ( res , 1000 ) ) ;
396
+
397
+ const commits = await fetchCommitsWithRetry ( owner , project , pr . number ) ;
398
+ if ( ! commits ) return null ;
399
+
400
+ const filteredCommits = commits . filter ( commit =>
401
+ commit . author ?. login === githubUsername &&
402
+ new Date ( commit . commit . author . date ) >= new Date ( startingDate ) &&
403
+ new Date ( commit . commit . author . date ) <= new Date ( endingDate )
404
+ ) ;
405
+
406
+ if ( filteredCommits . length === 0 ) return null ;
407
+
333
408
return {
334
409
pr,
335
- commits : commits . filter ( commit =>
336
- commit . author ?. login === githubUsername &&
337
- new Date ( commit . commit . author . date ) >= new Date ( startingDate ) &&
338
- new Date ( commit . commit . author . date ) <= new Date ( endingDate )
339
- )
410
+ commits : filteredCommits
340
411
} ;
341
- } catch ( err ) {
342
- console . error ( `Error fetching commits for PR #${ pr . number } :` , err ) ;
343
- return null ;
344
- }
345
- } ) ) ;
412
+ } ) ) ;
413
+
414
+ prCommits . push ( ...batchResults . filter ( Boolean ) ) ;
415
+ }
346
416
347
417
// Filter out null results and empty commits
348
418
const prCommitsData = prCommits
349
- . filter ( data => data && data . commits . length > 0 )
350
419
. reduce ( ( acc , { pr, commits } ) => {
351
420
const project = pr . repository_url . split ( '/' ) . pop ( ) ;
352
421
if ( ! acc [ project ] ) acc [ project ] = [ ] ;
0 commit comments