From 25aa738cd2c1aa68df84292ba9f8cd4ff3d9b2a9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 30 Jan 2024 17:16:03 +0100 Subject: [PATCH] WIP: Uses the wrong account when signed in with multiple GitHub.com accounts Fixes #5159 --- package.json | 9 +++++++++ src/commands.ts | 14 +++++++++++--- src/github/credentials.ts | 7 ++++++- src/github/folderRepositoryManager.ts | 6 +++++- src/github/githubRepository.ts | 9 +++++++-- src/github/repositoriesManager.ts | 14 ++++++++++---- src/view/treeNodes/categoryNode.ts | 17 +++++++++++++++-- 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 2c2304b06e..fcf4fab7d4 100644 --- a/package.json +++ b/package.json @@ -975,6 +975,11 @@ "title": "%command.pr.signinAndRefreshList.title%", "category": "%command.pull.request.category%" }, + { + "command": "pr.signinAlternateAndRefreshList", + "title": "%command.pr.signinAndRefreshList.title%", + "category": "%command.pull.request.category%" + }, { "command": "pr.configureRemotes", "title": "%command.pr.configureRemotes.title%", @@ -1664,6 +1669,10 @@ "command": "pr.signinAndRefreshList", "when": "false" }, + { + "command": "pr.signinAlternateAndRefreshList", + "when": "false" + }, { "command": "pr.copyCommitHash", "when": "false" diff --git a/src/commands.ts b/src/commands.ts index 79cb0b9105..c8684dd827 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -23,7 +23,7 @@ import { NotificationProvider } from './github/notifications'; import { GHPRComment, GHPRCommentThread, TemporaryComment } from './github/prComment'; import { PullRequestModel } from './github/pullRequestModel'; import { PullRequestOverviewPanel } from './github/pullRequestOverview'; -import { RepositoriesManager } from './github/repositoriesManager'; +import { AuthenticationType, RepositoriesManager } from './github/repositoriesManager'; import { getIssuesUrl, getPullsUrl, isInCodespaces, vscodeDevPrLink } from './github/utils'; import { PullRequestsTreeDataProvider } from './view/prsTreeDataProvider'; import { ReviewCommentController } from './view/reviewCommentController'; @@ -864,13 +864,13 @@ export function registerCommands( context.subscriptions.push( vscode.commands.registerCommand('pr.signinNoEnterprise', async () => { - await reposManager.authenticate(false); + await reposManager.authenticate(AuthenticationType.GitHub); }), ); context.subscriptions.push( vscode.commands.registerCommand('pr.signinenterprise', async () => { - await reposManager.authenticate(true); + await reposManager.authenticate(AuthenticationType.Enterprise); }), ); @@ -890,6 +890,14 @@ export function registerCommands( }), ); + context.subscriptions.push( + vscode.commands.registerCommand('pr.signinAlternateAndRefreshList', async () => { + if (await reposManager.authenticate(AuthenticationType.AlternateGitHub)) { + vscode.commands.executeCommand('pr.refreshList'); + } + }), + ); + context.subscriptions.push( vscode.commands.registerCommand('pr.configureRemotes', async () => { return vscode.commands.executeCommand('workbench.action.openSettings', `@ext:${EXTENSION_ID} remotes`); diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 77c403a893..68299dfedd 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -289,7 +289,7 @@ export class CredentialStore implements vscode.Disposable { } } - public async login(authProviderId: AuthProvider): Promise { + public async login(authProviderId: AuthProvider, useAlternateAccount: boolean = false): Promise { /* __GDPR__ "auth.start" : {} */ @@ -299,6 +299,11 @@ export class CredentialStore implements vscode.Disposable { let retry: boolean = true; let octokit: GitHub | undefined = undefined; const sessionOptions: vscode.AuthenticationGetSessionOptions = { createIfNone: true }; + if (useAlternateAccount) { + sessionOptions.clearSessionPreference = true; + sessionOptions.forceNewSession = true; + sessionOptions.createIfNone = undefined; + } let isCanceled: boolean = false; while (retry) { try { diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 0aa34c69b1..17e7f65549 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -35,7 +35,7 @@ import { git } from '../gitProviders/gitCommands'; import { OctokitCommon } from './common'; import { ConflictModel } from './conflictGuide'; import { CredentialStore } from './credentials'; -import { GitHubRepository, ItemsData, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository'; +import { GitHubRepository, ItemsData, ItemsResponseError, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository'; import { PullRequestState, UserResponse } from './graphql'; import { IAccount, ILabel, IMilestone, IProject, IPullRequestsPagingOptions, Issue, ITeam, MergeMethod, PRType, PullRequestMergeability, RepoAccessAndMergeMethods, User } from './interface'; import { IssueModel } from './issueModel'; @@ -60,6 +60,7 @@ export interface ItemsResponseResult { items: T[]; hasMorePages: boolean; hasUnsearchedRepositories: boolean; + error?: ItemsResponseError; } export class NoGitHubReposError extends Error { @@ -969,6 +970,7 @@ export class FolderRepositoryManager implements vscode.Disposable { if (page) { itemData.items = itemData.items.concat(page.items); itemData.hasMorePages = page.hasMorePages; + itemData.error = page.error ?? itemData.error; } }; @@ -1048,6 +1050,7 @@ export class FolderRepositoryManager implements vscode.Disposable { items: itemData.items, hasMorePages: pageInformation.hasMorePages, hasUnsearchedRepositories: i < githubRepositories.length - 1, + error: itemData.error }; } } @@ -1056,6 +1059,7 @@ export class FolderRepositoryManager implements vscode.Disposable { items: itemData.items, hasMorePages: false, hasUnsearchedRepositories: false, + error: itemData.error }; } diff --git a/src/github/githubRepository.ts b/src/github/githubRepository.ts index 0578552108..3d75535f94 100644 --- a/src/github/githubRepository.ts +++ b/src/github/githubRepository.ts @@ -73,9 +73,14 @@ export const PULL_REQUEST_PAGE_SIZE = 20; const GRAPHQL_COMPONENT_ID = 'GraphQL'; +export enum ItemsResponseError { + NotFound = 1 +} + export interface ItemsData { items: any[]; hasMorePages: boolean; + error?: ItemsResponseError; } export interface IssueData extends ItemsData { @@ -529,15 +534,15 @@ export class GitHubRepository implements vscode.Disposable { } catch (e) { Logger.error(`Fetching all pull requests failed: ${e}`, GitHubRepository.ID); if (e.code === 404) { - // not found + // not found, can also indicate that this is a private repo that the current user doesn't have access to. vscode.window.showWarningMessage( `Fetching pull requests for remote '${this.remote.remoteName}' failed, please check if the url ${this.remote.url} is valid.`, ); + return { items: [], hasMorePages: false, error: ItemsResponseError.NotFound }; } else { throw e; } } - return undefined; } async getPullRequestForBranch(branch: string, headOwner: string): Promise { diff --git a/src/github/repositoriesManager.ts b/src/github/repositoriesManager.ts index 6fb5065679..c293879a0c 100644 --- a/src/github/repositoriesManager.ts +++ b/src/github/repositoriesManager.ts @@ -28,6 +28,12 @@ export interface PullRequestDefaults { base: string; } +export enum AuthenticationType { + GitHub = 1, + Enterprise = 2, + AlternateGitHub = 3 +} + export class RepositoriesManager implements vscode.Disposable { static ID = 'RepositoriesManager'; @@ -181,14 +187,14 @@ export class RepositoriesManager implements vscode.Disposable { this.state = ReposManagerState.Initializing; } - async authenticate(enterprise?: boolean): Promise { - if (enterprise === false) { - return !!this._credentialStore.login(AuthProvider.github); + async authenticate(authenticationType: AuthenticationType = AuthenticationType.GitHub): Promise { + if (authenticationType !== AuthenticationType.Enterprise) { + return !!(await this._credentialStore.login(AuthProvider.github, authenticationType === AuthenticationType.AlternateGitHub)); } const { dotComRemotes, enterpriseRemotes, unknownRemotes } = await findDotComAndEnterpriseRemotes(this.folderManagers); const yes = vscode.l10n.t('Yes'); - if (enterprise) { + if (authenticationType === AuthenticationType.Enterprise) { const remoteToUse = getEnterpriseUri()?.toString() ?? (enterpriseRemotes.length ? enterpriseRemotes[0].normalizedHost : (unknownRemotes.length ? unknownRemotes[0].normalizedHost : undefined)); if (enterpriseRemotes.length === 0 && unknownRemotes.length === 0) { Logger.appendLine(`Enterprise login selected, but no possible enterprise remotes discovered (${dotComRemotes.length} .com)`); diff --git a/src/view/treeNodes/categoryNode.ts b/src/view/treeNodes/categoryNode.ts index ab030f2489..72025a4cf0 100644 --- a/src/view/treeNodes/categoryNode.ts +++ b/src/view/treeNodes/categoryNode.ts @@ -9,6 +9,7 @@ import { PR_SETTINGS_NAMESPACE, QUERIES } from '../../common/settingKeys'; import { ITelemetry } from '../../common/telemetry'; import { formatError } from '../../common/utils'; import { FolderRepositoryManager, ItemsResponseResult } from '../../github/folderRepositoryManager'; +import { ItemsResponseError } from '../../github/githubRepository'; import { PRType } from '../../github/interface'; import { NotificationProvider } from '../../github/notifications'; import { PullRequestModel } from '../../github/pullRequestModel'; @@ -25,6 +26,7 @@ export enum PRCategoryActionType { NoRemotes, NoMatchingRemotes, ConfigureRemotes, + RepositoryNotFound } interface QueryInspect { @@ -101,6 +103,14 @@ export class PRCategoryActionNode extends TreeNode implements vscode.TreeItem { arguments: [], }; break; + case PRCategoryActionType.RepositoryNotFound: + this.label = vscode.l10n.t('Repository not found. Try another account...'); + this.command = { + title: vscode.l10n.t('Sign in'), + command: 'pr.signinAlternateAndRefreshList', + arguments: [], + }; + break; default: break; } @@ -316,6 +326,7 @@ export class CategoryTreeNode extends TreeNode implements vscode.TreeItem { let hasMorePages = false; let hasUnsearchedRepositories = false; let needLogin = false; + let repositoryNotFound = false; if (this.type === PRType.LocalPullRequest) { try { this.prs = (await this._prsTreeModel.getLocalPullRequests(this._folderRepoManager)).items; @@ -334,7 +345,9 @@ export class CategoryTreeNode extends TreeNode implements vscode.TreeItem { response = await this._prsTreeModel.getPullRequestsForQuery(this._folderRepoManager, this.fetchNextPage, this._categoryQuery!); break; } - if (!this.fetchNextPage) { + if (response.error === ItemsResponseError.NotFound) { + repositoryNotFound = true; + } else if (!this.fetchNextPage) { this.prs = response.items; } else { this.prs = this.prs.concat(response.items); @@ -362,7 +375,7 @@ export class CategoryTreeNode extends TreeNode implements vscode.TreeItem { this.children = nodes; return nodes; } else { - const category = needLogin ? PRCategoryActionType.Login : PRCategoryActionType.Empty; + const category = needLogin ? PRCategoryActionType.Login : (repositoryNotFound ? PRCategoryActionType.RepositoryNotFound : PRCategoryActionType.Empty); const result = [new PRCategoryActionNode(this, category)]; this.children = result;