@@ -351,8 +351,26 @@ export class KodyRulesSyncService {
351351 repository . id ,
352352 ) ;
353353
354+ // Fetch PR details once — shared across all code paths below.
355+ const prDetails =
356+ await this . codeManagementService . getPullRequestByNumber ( {
357+ organizationAndTeamData,
358+ repository : { id : repository . id , name : repository . name } ,
359+ prNumber : pullRequestNumber ,
360+ } ) ;
361+
362+ const { head, base } = this . extractRefsFromPullRequest ( prDetails ) ;
363+ const pullRequestParam : any = {
364+ number : pullRequestNumber ,
365+ head : head ? { ref : head } : undefined ,
366+ base : base ? { ref : base } : undefined ,
367+ } ;
368+
354369 // If the sync is disabled, we need to force sync the files that have @kody-sync
355370 const forceSyncFiles : string [ ] = [ ] ;
371+ // Cache decoded file content from the @kody -sync scan so the
372+ // main loop below doesn't re-fetch the same files.
373+ const contentCache = new Map < string , string > ( ) ;
356374 if ( ! syncEnabled ) {
357375 // First, we need to check which files can be rule files
358376 const directoryPatterns = await this . getDirectoryPatterns (
@@ -369,25 +387,6 @@ export class KodyRulesSyncService {
369387 isRuleFile ( f . previous_filename ) ,
370388 ) ;
371389
372- // Get the PR details once
373- const prDetails =
374- await this . codeManagementService . getPullRequestByNumber ( {
375- organizationAndTeamData,
376- repository : {
377- id : repository . id ,
378- name : repository . name ,
379- } ,
380- prNumber : pullRequestNumber ,
381- } ) ;
382-
383- const { head, base } =
384- this . extractRefsFromPullRequest ( prDetails ) ;
385- const pullRequestParam : any = {
386- number : pullRequestNumber ,
387- head : head ? { ref : head } : undefined ,
388- base : base ? { ref : base } : undefined ,
389- } ;
390-
391390 // Now we need to check which files have @kody -sync in the content
392391 for ( const f of ruleChanges ) {
393392 if ( f . status === 'removed' ) continue ;
@@ -402,18 +401,23 @@ export class KodyRulesSyncService {
402401 pullRequest : pullRequestParam ,
403402 } ) ;
404403
405- if ( content && this . shouldForceSync ( content ) ) {
406- forceSyncFiles . push ( f . filename ) ;
407- this . logger . log ( {
408- message :
409- 'File marked for force sync with @kody-sync' ,
410- context : KodyRulesSyncService . name ,
411- metadata : {
412- filename : f . filename ,
413- repositoryId : repository . id ,
414- organizationAndTeamData,
415- } ,
416- } ) ;
404+ if ( content ) {
405+ // Cache for reuse in the main loop below.
406+ contentCache . set ( f . filename , content ) ;
407+
408+ if ( this . shouldForceSync ( content ) ) {
409+ forceSyncFiles . push ( f . filename ) ;
410+ this . logger . log ( {
411+ message :
412+ 'File marked for force sync with @kody-sync' ,
413+ context : KodyRulesSyncService . name ,
414+ metadata : {
415+ filename : f . filename ,
416+ repositoryId : repository . id ,
417+ organizationAndTeamData,
418+ } ,
419+ } ) ;
420+ }
417421 }
418422 }
419423
@@ -476,20 +480,6 @@ export class KodyRulesSyncService {
476480 } ) ;
477481 }
478482
479- const prDetails =
480- await this . codeManagementService . getPullRequestByNumber ( {
481- organizationAndTeamData,
482- repository : { id : repository . id , name : repository . name } ,
483- prNumber : pullRequestNumber ,
484- } ) ;
485-
486- const { head, base } = this . extractRefsFromPullRequest ( prDetails ) ;
487- const pullRequestParam : any = {
488- number : pullRequestNumber ,
489- head : head ? { ref : head } : undefined ,
490- base : base ? { ref : base } : undefined ,
491- } ;
492-
493483 const directoryPatterns = await this . getDirectoryPatterns (
494484 organizationAndTeamData ,
495485 repository . id ,
@@ -531,87 +521,94 @@ export class KodyRulesSyncService {
531521 ? f . previous_filename
532522 : f . filename ;
533523
534- const contentResp =
535- await this . codeManagementService . getRepositoryContentFile ( {
536- organizationAndTeamData,
537- repository : {
538- id : repository . id ,
539- name : repository . name ,
540- } ,
541- file : { filename : f . filename } ,
542- pullRequest : pullRequestParam ,
543- } ) ;
544- // Fallbacks if the source branch was deleted on merge (e.g., GitLab):
545- // 1) Try with base as head
546- // 2) Try with default branch as head
547- let effectiveContent = contentResp ;
548- if ( ! effectiveContent ?. data ?. content ) {
549- const baseRef = pullRequestParam . base ?. ref ;
550- if ( baseRef ) {
551- try {
552- const baseAsHead =
553- await this . codeManagementService . getRepositoryContentFile (
554- {
555- organizationAndTeamData,
556- repository : {
557- id : repository . id ,
558- name : repository . name ,
524+ // Reuse cached content from the @kody -sync scan when
525+ // available to avoid a duplicate API call for the same
526+ // file (the scan already fetched it moments ago).
527+ let decoded : string | null = contentCache . get ( f . filename ) ?? null ;
528+
529+ if ( ! decoded ) {
530+ const contentResp =
531+ await this . codeManagementService . getRepositoryContentFile ( {
532+ organizationAndTeamData,
533+ repository : {
534+ id : repository . id ,
535+ name : repository . name ,
536+ } ,
537+ file : { filename : f . filename } ,
538+ pullRequest : pullRequestParam ,
539+ } ) ;
540+ // Fallbacks if the source branch was deleted on merge (e.g., GitLab):
541+ // 1) Try with base as head
542+ // 2) Try with default branch as head
543+ let effectiveContent = contentResp ;
544+ if ( ! effectiveContent ?. data ?. content ) {
545+ const baseRef = pullRequestParam . base ?. ref ;
546+ if ( baseRef ) {
547+ try {
548+ const baseAsHead =
549+ await this . codeManagementService . getRepositoryContentFile (
550+ {
551+ organizationAndTeamData,
552+ repository : {
553+ id : repository . id ,
554+ name : repository . name ,
555+ } ,
556+ file : { filename : f . filename } ,
557+ pullRequest : { head : { ref : baseRef } } ,
559558 } ,
560- file : { filename : f . filename } ,
561- pullRequest : { head : { ref : baseRef } } ,
562- } ,
563- ) ;
564- if ( baseAsHead ?. data ?. content ) {
565- effectiveContent = baseAsHead ;
559+ ) ;
560+ if ( baseAsHead ?. data ?. content ) {
561+ effectiveContent = baseAsHead ;
562+ }
563+ } catch {
564+ // Ignore error
566565 }
567- } catch {
568- // Ignore error
569566 }
570567 }
571- }
572- if ( ! effectiveContent ?. data ?. content ) {
573- try {
574- const defaultBranch =
575- await this . codeManagementService . getDefaultBranch ( {
576- organizationAndTeamData,
577- repository : {
578- id : repository . id ,
579- name : repository . name ,
580- } ,
581- } ) ;
582- if ( defaultBranch ) {
583- const defAsHead =
584- await this . codeManagementService . getRepositoryContentFile (
585- {
586- organizationAndTeamData,
587- repository : {
588- id : repository . id ,
589- name : repository . name ,
590- } ,
591- file : { filename : f . filename } ,
592- pullRequest : {
593- head : { ref : defaultBranch } ,
594- } ,
568+ if ( ! effectiveContent ?. data ?. content ) {
569+ try {
570+ const defaultBranch =
571+ await this . codeManagementService . getDefaultBranch ( {
572+ organizationAndTeamData,
573+ repository : {
574+ id : repository . id ,
575+ name : repository . name ,
595576 } ,
596- ) ;
597- if ( defAsHead ?. data ?. content ) {
598- effectiveContent = defAsHead ;
577+ } ) ;
578+ if ( defaultBranch ) {
579+ const defAsHead =
580+ await this . codeManagementService . getRepositoryContentFile (
581+ {
582+ organizationAndTeamData,
583+ repository : {
584+ id : repository . id ,
585+ name : repository . name ,
586+ } ,
587+ file : { filename : f . filename } ,
588+ pullRequest : {
589+ head : { ref : defaultBranch } ,
590+ } ,
591+ } ,
592+ ) ;
593+ if ( defAsHead ?. data ?. content ) {
594+ effectiveContent = defAsHead ;
595+ }
599596 }
597+ } catch {
598+ // Ignore error
600599 }
601- } catch {
602- // Ignore error
603600 }
604- }
605601
606- const rawContent = effectiveContent ?. data ?. content ;
607- if ( ! rawContent ) {
608- continue ;
609- }
602+ const rawContent = effectiveContent ?. data ?. content ;
603+ if ( ! rawContent ) {
604+ continue ;
605+ }
610606
611- const decoded =
612- contentResp ?. data ?. encoding === 'base64'
613- ? Buffer . from ( rawContent , 'base64' ) . toString ( 'utf-8' )
614- : rawContent ;
607+ decoded =
608+ effectiveContent ?. data ?. encoding === 'base64'
609+ ? Buffer . from ( rawContent , 'base64' ) . toString ( 'utf-8' )
610+ : rawContent ;
611+ }
615612
616613 //Verify if the file should be ignored due to the @kody -ignore marker
617614 if ( this . shouldIgnoreFile ( decoded ) ) {
0 commit comments