diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 11b1d3d649a..021ce31353a 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -11,21 +11,25 @@ import { LinkContext, getLink, getVscodeDevHost } from './links'; async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) { try { - const permalink = getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange); + const permalink = await getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange); if (permalink) { return vscode.env.clipboard.writeText(permalink); } } catch (err) { - vscode.window.showErrorMessage(err.message); + if (!(err instanceof vscode.CancellationError)) { + vscode.window.showErrorMessage(err.message); + } } } async function openVscodeDevLink(gitAPI: GitAPI): Promise { try { - const headlink = getLink(gitAPI, true, getVscodeDevHost(), 'headlink'); + const headlink = await getLink(gitAPI, true, getVscodeDevHost(), 'headlink'); return headlink ? vscode.Uri.parse(headlink) : undefined; } catch (err) { - vscode.window.showErrorMessage(err.message); + if (!(err instanceof vscode.CancellationError)) { + vscode.window.showErrorMessage(err.message); + } return undefined; } } diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 97fb3218a2a..47f9b5b4148 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { API as GitAPI, Repository } from './typings/git'; +import { API as GitAPI, RefType, Repository } from './typings/git'; import { getRepositoryFromUrl } from './util'; export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean { @@ -129,7 +129,7 @@ export function encodeURIComponentExceptSlashes(path: string) { return path.split('/').map((segment) => encodeURIComponent(segment)).join('/'); } -export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): string | undefined { +export async function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): Promise { hostPrefix = hostPrefix ?? 'https://github.com'; const fileAndPosition = getFileAndPosition(context); if (!fileAndPosition) { @@ -142,6 +142,9 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri if (!gitRepo) { return; } + + await ensurePublished(gitRepo, uri); + let repo: { owner: string; repo: string } | undefined; gitRepo.state.remotes.find(remote => { if (remote.fetchUrl) { @@ -182,3 +185,54 @@ export function getBranchLink(url: string, branch: string, hostPrefix: string = export function getVscodeDevHost(): string { return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; } + +export async function ensurePublished(repository: Repository, file: vscode.Uri) { + if ((repository.state.HEAD?.type === RefType.Head || repository.state.HEAD?.type === RefType.Tag) + // If HEAD is not published, make sure it is + && !repository?.state.HEAD?.upstream + ) { + const publishBranch = vscode.l10n.t('Publish Branch'); + const selection = await vscode.window.showInformationMessage( + vscode.l10n.t('The current branch is not published to the remote. Would you like to publish your branch before copying a link?'), + { modal: true }, + publishBranch + ); + if (selection !== publishBranch) { + throw new vscode.CancellationError(); + } + + await vscode.commands.executeCommand('git.publish'); + } + + const uncommittedChanges = [...repository.state.workingTreeChanges, ...repository.state.indexChanges]; + if (uncommittedChanges.find((c) => c.uri.toString() === file.toString())) { + const commitChanges = vscode.l10n.t('Commit Changes'); + const copyAnyway = vscode.l10n.t('Copy Anyway'); + const selection = await vscode.window.showWarningMessage( + vscode.l10n.t('The current file has uncommitted changes. Please commit your changes before copying a link.'), + { modal: true }, + commitChanges, + copyAnyway + ); + + if (selection !== copyAnyway) { + // Focus the SCM view + vscode.commands.executeCommand('workbench.view.scm'); + throw new vscode.CancellationError(); + } + } else if (repository.state.HEAD?.ahead) { + const pushCommits = vscode.l10n.t('Push Commits'); + const selection = await vscode.window.showInformationMessage( + vscode.l10n.t('The current branch has unpublished commits. Would you like to push your commits before copying a link?'), + { modal: true }, + pushCommits + ); + if (selection !== pushCommits) { + throw new vscode.CancellationError(); + } + + await repository.push(); + } + + await repository.status(); +} diff --git a/extensions/github/src/shareProviders.ts b/extensions/github/src/shareProviders.ts index 78cdb3bd175..7aea9c27b24 100644 --- a/extensions/github/src/shareProviders.ts +++ b/extensions/github/src/shareProviders.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { API } from './typings/git'; import { getRepositoryFromUrl, repositoryHasGitHubRemote } from './util'; -import { encodeURIComponentExceptSlashes, getRepositoryForFile, notebookCellRangeString, rangeString } from './links'; +import { encodeURIComponentExceptSlashes, ensurePublished, getRepositoryForFile, notebookCellRangeString, rangeString } from './links'; export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disposable { readonly id: string = 'copyVscodeDevLink'; @@ -63,12 +63,14 @@ export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disp } } - provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): vscode.ProviderResult { + async provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): Promise { const repository = getRepositoryForFile(this.gitAPI, item.resourceUri); if (!repository) { return; } + await ensurePublished(repository, item.resourceUri); + let repo: { owner: string; repo: string } | undefined; repository.state.remotes.find(remote => { if (remote.fetchUrl) {