@@ -68,7 +68,15 @@ export class ReviewManager extends Disposable {
6868 } ;
6969
7070 private _switchingToReviewMode : boolean ;
71+ private _stateValidated : boolean = false ;
7172 private _changesSinceLastReviewProgress : ProgressHelper = new ProgressHelper ( ) ;
73+ /**
74+ * Cached max PR numbers per repo, keyed by `owner/repo`.
75+ * Used to skip expensive `checkGitHubForPrBranch` calls when no new PRs
76+ * have been created since the last check for the current branch.
77+ */
78+ private _cachedMaxPRNumbers : Map < string , number > | undefined ;
79+ private _cachedBranchName : string | undefined ;
7280 /**
7381 * Flag set when the "Checkout" action is used and cleared on the next git
7482 * state update, once review mode has been entered. Used to disambiguate
@@ -127,6 +135,7 @@ export class ReviewManager extends Disposable {
127135 if ( _gitApi . state === 'initialized' ) {
128136 this . updateState ( true ) ;
129137 }
138+ this . pollForStateChange ( ) ;
130139 }
131140
132141 private registerListeners ( ) : void {
@@ -204,6 +213,16 @@ export class ReviewManager extends Disposable {
204213 this . _register ( GitHubCreatePullRequestLinkProvider . registerProvider ( this , this . _folderRepoManager ) ) ;
205214 }
206215
216+ private pollForStateChange ( ) {
217+ setTimeout ( async ( ) => {
218+ Logger . appendLine ( 'Polling for state change...' , this . id ) ;
219+ if ( ! this . _validateStatusInProgress && ! this . _folderRepoManager . activePullRequest ) {
220+ await this . updateState ( ) ;
221+ }
222+ this . pollForStateChange ( ) ;
223+ } , 1000 * 60 * 5 ) ;
224+ }
225+
207226 private async updateBaseBranchMetadata ( oldHead : Branch , newHead : Branch ) {
208227 if ( ! oldHead . commit || ( oldHead . commit !== newHead . commit ) || ! newHead . name || ! oldHead . name || ( oldHead . name === newHead . name ) ) {
209228 return ;
@@ -324,13 +343,19 @@ export class ReviewManager extends Disposable {
324343
325344 const validatePromise = new Promise < void > ( resolve => {
326345 this . validateStateAndResetPromise ( silent , updateLayout ) . then ( ( ) => {
327- vscode . commands . executeCommand ( 'setContext' , 'github:stateValidated' , true ) . then ( ( ) => {
346+ const done = ( ) => {
328347 if ( timeout ) {
329348 clearTimeout ( timeout ) ;
330349 timeout = undefined ;
331350 }
332351 resolve ( ) ;
333- } ) ;
352+ } ;
353+ if ( ! this . _stateValidated ) {
354+ this . _stateValidated = true ;
355+ commands . setContext ( 'github:stateValidated' , true ) . then ( done ) ;
356+ } else {
357+ done ( ) ;
358+ }
334359 } ) ;
335360 } ) ;
336361
@@ -396,6 +421,68 @@ export class ReviewManager extends Disposable {
396421 }
397422 }
398423
424+ /**
425+ * Returns the GitHub repos relevant to the current branch: the head repo
426+ * (matching the branch's remote) and its parent repos (for forks, the upstream).
427+ */
428+ private async getRelevantGitHubRepos ( ) : Promise < GitHubRepository [ ] > {
429+ const remoteName = this . _repository . state . HEAD ?. remote ?? this . _repository . state . HEAD ?. upstream ?. remote ;
430+ if ( ! remoteName ) {
431+ return [ ] ;
432+ }
433+ const headRepo = this . _folderRepoManager . gitHubRepositories . find (
434+ repo => repo . remote . remoteName === remoteName ,
435+ ) ;
436+ if ( ! headRepo ) {
437+ return [ ] ;
438+ }
439+ const metadata = await headRepo . getMetadata ( ) ;
440+ if ( ! metadata ?. owner ) {
441+ return [ headRepo ] ;
442+ }
443+ const parentRepos = this . _folderRepoManager . gitHubRepositories . filter ( repo => {
444+ if ( metadata . fork ) {
445+ return repo . remote . owner === metadata . parent ?. owner ?. login && repo . remote . repositoryName === metadata . parent ?. name ;
446+ } else {
447+ return repo . remote . owner === metadata . owner ?. login && repo . remote . repositoryName === metadata . name ;
448+ }
449+ } ) ;
450+ // Include both head and parent repos, deduplicated
451+ const result = new Set < GitHubRepository > ( parentRepos ) ;
452+ result . add ( headRepo ) ;
453+ return [ ...result ] ;
454+ }
455+
456+ private async getMaxPullRequestNumbers ( ) : Promise < Map < string , number > > {
457+ const result = new Map < string , number > ( ) ;
458+ const repos = await this . getRelevantGitHubRepos ( ) ;
459+ await Promise . all ( repos . map ( async repo => {
460+ const max = await repo . getMaxPullRequest ( ) ;
461+ if ( max !== undefined ) {
462+ result . set ( `${ repo . remote . owner } /${ repo . remote . repositoryName } ` , max ) ;
463+ }
464+ } ) ) ;
465+ return result ;
466+ }
467+
468+ private async hasNewPullRequests ( ) : Promise < boolean > {
469+ if ( ! this . _cachedMaxPRNumbers ) {
470+ this . _cachedMaxPRNumbers = await this . getMaxPullRequestNumbers ( ) ;
471+ return true ;
472+ }
473+ const current = await this . getMaxPullRequestNumbers ( ) ;
474+ let result = false ;
475+ for ( const [ key , value ] of current ) {
476+ const cached = this . _cachedMaxPRNumbers . get ( key ) ;
477+ if ( cached === undefined || value > cached ) {
478+ result = true ;
479+ break ;
480+ }
481+ }
482+ this . _cachedMaxPRNumbers = current ;
483+ return result ;
484+ }
485+
399486 private async checkGitHubForPrBranch ( branch : Branch ) : Promise < ( PullRequestMetadata & { model : PullRequestModel } ) | undefined > {
400487 try {
401488 let branchToCheck : Branch ;
@@ -459,6 +546,9 @@ export class ReviewManager extends Disposable {
459546 await this . clear ( true ) ;
460547 return ;
461548 }
549+ if ( ! this . _cachedMaxPRNumbers ) {
550+ this . _cachedMaxPRNumbers = await this . getMaxPullRequestNumbers ( ) ;
551+ }
462552
463553 const branch = this . _repository . state . HEAD ;
464554 const ignoreBranches = vscode . workspace . getConfiguration ( PR_SETTINGS_NAMESPACE ) . get < string [ ] > ( IGNORE_PR_BRANCHES ) ;
@@ -473,8 +563,13 @@ export class ReviewManager extends Disposable {
473563
474564 if ( ! matchingPullRequestMetadata ) {
475565 Logger . appendLine ( `No matching pull request metadata found locally for current branch ${ branch . name } ` , this . id ) ;
476- matchingPullRequestMetadata = await this . checkGitHubForPrBranch ( branch ) ;
566+ if ( this . _cachedBranchName !== branch . name || await this . hasNewPullRequests ( ) ) {
567+ matchingPullRequestMetadata = await this . checkGitHubForPrBranch ( branch ) ;
568+ } else {
569+ Logger . appendLine ( `Skipping GitHub check for branch ${ branch . name } : no new PRs since last check` , this . id ) ;
570+ }
477571 }
572+ this . _cachedBranchName = branch . name ;
478573
479574 if ( ! matchingPullRequestMetadata ) {
480575 Logger . appendLine (
0 commit comments