diff --git a/src/github/createPRViewProvider.ts b/src/github/createPRViewProvider.ts index d031cdb98a..10c6b02bf8 100644 --- a/src/github/createPRViewProvider.ts +++ b/src/github/createPRViewProvider.ts @@ -10,7 +10,7 @@ import { PullRequestDefaults, titleAndBodyFrom, } from './folderRepositoryManager'; -import { GitHubRepository } from './githubRepository'; +import { GitHubRepository, isRateLimitError, ViewerPermission } from './githubRepository'; import { IAccount, ILabel, IMilestone, IProject, isITeam, ITeam, MergeMethod, RepoAccessAndMergeMethods } from './interface'; import { BaseBranchMetadata, PullRequestGitHelper } from './pullRequestGitHelper'; import { PullRequestModel } from './pullRequestModel'; @@ -211,13 +211,47 @@ export abstract class BaseCreatePullRequestViewProvider(DEFAULT_CREATE_OPTION, 'lastUsed'); const lastCreateMethod: { autoMerge: boolean, mergeMethod: MergeMethod | undefined, isDraft: boolean } | undefined = this._folderRepositoryManager.context.workspaceState.get<{ autoMerge: boolean, mergeMethod: MergeMethod, isDraft } | undefined>(PREVIOUS_CREATE_METHOD, undefined); @@ -994,7 +1028,18 @@ Don't forget to commit your template file to the repository so that it can be us } private async processRemoteAndBranchResult(githubRepository: GitHubRepository, result: { remote: RemoteInfo, branch: string }, isBase: boolean) { - const [defaultBranch, viewerPermission] = await Promise.all([githubRepository.getDefaultBranch(), githubRepository.getViewerPermission()]); + let viewerPermission: ViewerPermission; + try { + viewerPermission = await githubRepository.getViewerPermission(); + } catch (e) { + if (isRateLimitError(e)) { + vscode.window.showErrorMessage(vscode.l10n.t('GitHub API rate limit exceeded. Please wait and try again.')); + viewerPermission = ViewerPermission.Unknown; + } else { + throw e; + } + } + const defaultBranch = await githubRepository.getDefaultBranch(); commands.setContext(contexts.CREATE_PR_PERMISSIONS, viewerPermission); let chooseResult: ChooseBaseRemoteAndBranchResult | ChooseCompareRemoteAndBranchResult; diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 951476efc5..d4738c0030 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -11,7 +11,7 @@ import { ConflictModel } from './conflictGuide'; import { ConflictResolutionCoordinator } from './conflictResolutionCoordinator'; import { Conflict, ConflictResolutionModel } from './conflictResolutionModel'; import { CredentialStore } from './credentials'; -import { CopilotWorkingStatus, GitHubRepository, ItemsData, PULL_REQUEST_PAGE_SIZE, PullRequestChangeEvent, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository'; +import { CopilotWorkingStatus, GitHubRepository, isRateLimitError, ItemsData, PULL_REQUEST_PAGE_SIZE, PullRequestChangeEvent, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository'; import { PullRequestResponse, PullRequestState } from './graphql'; import { IAccount, ILabel, IMilestone, IProject, IPullRequestsPagingOptions, Issue, ITeam, MergeMethod, PRType, PullRequestMergeability, RepoAccessAndMergeMethods, User } from './interface'; import { IssueModel } from './issueModel'; @@ -2957,7 +2957,16 @@ export class FolderRepositoryManager extends Disposable { pushRemote, this.credentialStore, ); - const permission = await githubRepo.getViewerPermission(); + let permission: ViewerPermission; + try { + permission = await githubRepo.getViewerPermission(); + } catch (e) { + if (isRateLimitError(e)) { + vscode.window.showErrorMessage(vscode.l10n.t('GitHub API rate limit exceeded. Please wait and try again.'), { modal: true }); + return; + } + throw e; + } let selectedRemote: GitHubRemote | undefined; if ( permission === ViewerPermission.Read || diff --git a/src/github/githubRepository.ts b/src/github/githubRepository.ts index f60942ed46..7b0c608142 100644 --- a/src/github/githubRepository.ts +++ b/src/github/githubRepository.ts @@ -124,6 +124,26 @@ export enum ViewerPermission { Write = 'WRITE', } +export class RateLimitError extends Error { + constructor(message?: string) { + super(message ?? 'GitHub API rate limit exceeded'); + this.name = 'RateLimitError'; + } +} + +export function isRateLimitError(e: unknown): boolean { + if (e instanceof RateLimitError) { + return true; + } + if (e instanceof Error) { + const msg = e.message.toLowerCase(); + if (msg.includes('rate limit') || msg.includes('secondary rate') || msg.includes('abuse detection')) { + return true; + } + } + return false; +} + export enum TeamReviewerRefreshKind { None, Try, @@ -941,6 +961,9 @@ export class GitHubRepository extends Disposable { return parseGraphQLViewerPermission(data); } catch (e) { Logger.error(`Unable to fetch viewer permission: ${e}`, this.id); + if (isRateLimitError(e)) { + throw new RateLimitError(); + } return ViewerPermission.Unknown; } }