From 1ecee9dbd700c01d718aa5e106a2f72c58852d74 Mon Sep 17 00:00:00 2001 From: Shawn Esquivel <94336773+shawnesquivel@users.noreply.github.com> Date: Fri, 3 Mar 2023 08:15:18 -0800 Subject: [PATCH] bug: modified password placeholder text depending if remote URI is github (Fix #1176) (#1220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modified password placeholder text to only include PAT * changed placeholder text if remote URI matches github https * removed redundant type annotation * get remote for no-auth git pull * cleaned code and added catch for git clone * removed comment * removed old code * fixed no-case-declarations error in the catch switch block * fixed and ignored no-case-declaration errors * removed redundant type annotation * Update src/commandsAndMenu.tsx Co-authored-by: Frédéric Collonval * updated translation strings and simplified gitPull * make variable names more descriptive --------- Co-authored-by: Frédéric Collonval --- src/cloneCommand.ts | 1 + src/commandsAndMenu.tsx | 78 +++++++++++++++++++++++++++++++++- src/git.ts | 3 +- src/tokens.ts | 6 +++ src/widgets/CredentialsBox.tsx | 9 ++-- 5 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/cloneCommand.ts b/src/cloneCommand.ts index 51a0d5ec5..502e94f9b 100644 --- a/src/cloneCommand.ts +++ b/src/cloneCommand.ts @@ -79,6 +79,7 @@ export const gitCloneCommandPlugin: JupyterFrontEndPlugin = { level: Level.ERROR, error: error as Error }); + throw error; } } } diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx index 26c2baf7e..fdfa2a550 100644 --- a/src/commandsAndMenu.tsx +++ b/src/commandsAndMenu.tsx @@ -1571,6 +1571,36 @@ export async function showGitOperationDialog( authentication?: Git.IAuth, retry = false ): Promise { + /** + * Returns the current remote's URL based on the current remote name and all the remotes + */ + async function getCurrentRemote(currentRemoteName: string): Promise { + const remoteList = await model.getRemotes().then(remoteList => { + return remoteList; + }); + + const currentRemote = remoteList.find( + remoteURI => remoteURI.name === currentRemoteName + ); + + if (currentRemote) { + return currentRemote?.url; + } else { + return ''; + } + } + + /** + * Returns the Git provider based on the domain name of the url + */ + function getGitProviderHost(remoteUrl: string): string { + // Regex returns the word between "https" and "." + const re = /https:\/\/([^.]+)\./; + const result = remoteUrl.match(re) ?? []; + const gitProvider = result[1]; + return gitProvider; + } + try { let result: Git.IResultWithMessage; // the Git action @@ -1612,7 +1642,6 @@ export async function showGitOperationDialog( result = { code: -1, message: 'Unknown git command' }; break; } - return result.message; } catch (error) { if ( @@ -1620,13 +1649,58 @@ export async function showGitOperationDialog( errorMessage => (error as Error).message.indexOf(errorMessage) > -1 ) ) { + // Change the placeholder message for GitHub + let gitPasswordPlaceholder = trans.__('password / personal access token'); + let remoteGitProvider = ''; + + switch (operation) { + case Operation.Clone: + // eslint-disable-next-line no-case-declarations + const { url: encodedArgsUrl } = args as any as IGitCloneArgs; + remoteGitProvider = getGitProviderHost( + decodeURIComponent(encodedArgsUrl) + ); + break; + case Operation.Push: + case Operation.ForcePush: + case Operation.Pull: + // If the remote is defined, check it against the remote URI list + if (model.currentBranch.upstream) { + // Compare the remote against the URI list + const remoteName = model.currentBranch.upstream.split('/')[0]; + const currentRemoteUrl = await getCurrentRemote(remoteName); + remoteGitProvider = currentRemoteUrl + ? getGitProviderHost(currentRemoteUrl) + : ''; + } else { + // if the remote is undefined, use first remote URI + const remoteList = await model.getRemotes().then(remoteList => { + return remoteList; + }); + remoteGitProvider = getGitProviderHost(remoteList[0]?.url); + } + break; + + case Operation.Fetch: + remoteGitProvider = await model + .getRemotes() + .then(remoteList => remoteList[0]?.url); + break; + default: + break; + } + // GitHub only verifies with personal access tokens + if (remoteGitProvider && remoteGitProvider.toLowerCase() === 'github') { + gitPasswordPlaceholder = trans.__('personal access token'); + } // If the error is an authentication error, ask the user credentials const credentials = await showDialog({ title: trans.__('Git credentials required'), body: new GitCredentialsForm( trans, trans.__('Enter credentials for remote repository'), - retry ? trans.__('Incorrect username or password.') : '' + retry ? trans.__('Incorrect username or password.') : '', + gitPasswordPlaceholder ) }); diff --git a/src/git.ts b/src/git.ts index 69e41aa89..f54fac670 100644 --- a/src/git.ts +++ b/src/git.ts @@ -9,7 +9,8 @@ import { Git } from './tokens'; export const AUTH_ERROR_MESSAGES = [ 'Invalid username or password', 'could not read Username', - 'could not read Password' + 'could not read Password', + 'Authentication error' ]; /** diff --git a/src/tokens.ts b/src/tokens.ts index 45435a27a..96604c19e 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -360,6 +360,12 @@ export interface IGitExtension extends IDisposable { */ getRelativeFilePath(path?: string): string | null; + /** + * Show remote repository for the current repository + * @returns promise which resolves to a list of remote repositories + */ + getRemotes(): Promise; + /** * Add an entry in .gitignore file * diff --git a/src/widgets/CredentialsBox.tsx b/src/widgets/CredentialsBox.tsx index 9e39c24e7..8a28d0fb2 100755 --- a/src/widgets/CredentialsBox.tsx +++ b/src/widgets/CredentialsBox.tsx @@ -10,13 +10,16 @@ export class GitCredentialsForm extends Widget implements Dialog.IBodyWidget { + private _passwordPlaceholder: string; constructor( trans: TranslationBundle, textContent = trans.__('Enter credentials for remote repository'), - warningContent = '' + warningContent = '', + passwordPlaceholder = trans.__('password / personal access token') ) { super(); this._trans = trans; + this._passwordPlaceholder = passwordPlaceholder; this.node.appendChild(this.createBody(textContent, warningContent)); } @@ -41,9 +44,7 @@ export class GitCredentialsForm text.textContent = textContent; warning.textContent = warningContent; this._user.placeholder = this._trans.__('username'); - this._password.placeholder = this._trans.__( - 'password / personal access token' - ); + this._password.placeholder = this._passwordPlaceholder; checkboxLabel.className = 'jp-CredentialsBox-label-checkbox'; this._checkboxCacheCredentials.type = 'checkbox';