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';