Skip to content

Commit c9a03e5

Browse files
alexr00Copilot
andauthored
Poll if a PR was created for the current branch (#8674)
Fixes #8643 Co-authored-by: Copilot <copilot@github.com>
1 parent 239b61a commit c9a03e5

File tree

1 file changed

+98
-3
lines changed

1 file changed

+98
-3
lines changed

src/view/reviewManager.ts

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)