Skip to content

Commit 31478ce

Browse files
authored
Ask user to commit/push changes when copying links (microsoft#185802)
* Ask user to commit/push changes when copying links * Don't show an error message for cancellation errors
1 parent 838ff62 commit 31478ce

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

extensions/github/src/commands.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,25 @@ import { LinkContext, getLink, getVscodeDevHost } from './links';
1111

1212
async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) {
1313
try {
14-
const permalink = getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange);
14+
const permalink = await getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange);
1515
if (permalink) {
1616
return vscode.env.clipboard.writeText(permalink);
1717
}
1818
} catch (err) {
19-
vscode.window.showErrorMessage(err.message);
19+
if (!(err instanceof vscode.CancellationError)) {
20+
vscode.window.showErrorMessage(err.message);
21+
}
2022
}
2123
}
2224

2325
async function openVscodeDevLink(gitAPI: GitAPI): Promise<vscode.Uri | undefined> {
2426
try {
25-
const headlink = getLink(gitAPI, true, getVscodeDevHost(), 'headlink');
27+
const headlink = await getLink(gitAPI, true, getVscodeDevHost(), 'headlink');
2628
return headlink ? vscode.Uri.parse(headlink) : undefined;
2729
} catch (err) {
28-
vscode.window.showErrorMessage(err.message);
30+
if (!(err instanceof vscode.CancellationError)) {
31+
vscode.window.showErrorMessage(err.message);
32+
}
2933
return undefined;
3034
}
3135
}

extensions/github/src/links.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { API as GitAPI, Repository } from './typings/git';
7+
import { API as GitAPI, RefType, Repository } from './typings/git';
88
import { getRepositoryFromUrl } from './util';
99

1010
export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean {
@@ -129,7 +129,7 @@ export function encodeURIComponentExceptSlashes(path: string) {
129129
return path.split('/').map((segment) => encodeURIComponent(segment)).join('/');
130130
}
131131

132-
export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): string | undefined {
132+
export async function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): Promise<string | undefined> {
133133
hostPrefix = hostPrefix ?? 'https://github.com';
134134
const fileAndPosition = getFileAndPosition(context);
135135
if (!fileAndPosition) {
@@ -142,6 +142,9 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri
142142
if (!gitRepo) {
143143
return;
144144
}
145+
146+
await ensurePublished(gitRepo, uri);
147+
145148
let repo: { owner: string; repo: string } | undefined;
146149
gitRepo.state.remotes.find(remote => {
147150
if (remote.fetchUrl) {
@@ -182,3 +185,54 @@ export function getBranchLink(url: string, branch: string, hostPrefix: string =
182185
export function getVscodeDevHost(): string {
183186
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
184187
}
188+
189+
export async function ensurePublished(repository: Repository, file: vscode.Uri) {
190+
if ((repository.state.HEAD?.type === RefType.Head || repository.state.HEAD?.type === RefType.Tag)
191+
// If HEAD is not published, make sure it is
192+
&& !repository?.state.HEAD?.upstream
193+
) {
194+
const publishBranch = vscode.l10n.t('Publish Branch');
195+
const selection = await vscode.window.showInformationMessage(
196+
vscode.l10n.t('The current branch is not published to the remote. Would you like to publish your branch before copying a link?'),
197+
{ modal: true },
198+
publishBranch
199+
);
200+
if (selection !== publishBranch) {
201+
throw new vscode.CancellationError();
202+
}
203+
204+
await vscode.commands.executeCommand('git.publish');
205+
}
206+
207+
const uncommittedChanges = [...repository.state.workingTreeChanges, ...repository.state.indexChanges];
208+
if (uncommittedChanges.find((c) => c.uri.toString() === file.toString())) {
209+
const commitChanges = vscode.l10n.t('Commit Changes');
210+
const copyAnyway = vscode.l10n.t('Copy Anyway');
211+
const selection = await vscode.window.showWarningMessage(
212+
vscode.l10n.t('The current file has uncommitted changes. Please commit your changes before copying a link.'),
213+
{ modal: true },
214+
commitChanges,
215+
copyAnyway
216+
);
217+
218+
if (selection !== copyAnyway) {
219+
// Focus the SCM view
220+
vscode.commands.executeCommand('workbench.view.scm');
221+
throw new vscode.CancellationError();
222+
}
223+
} else if (repository.state.HEAD?.ahead) {
224+
const pushCommits = vscode.l10n.t('Push Commits');
225+
const selection = await vscode.window.showInformationMessage(
226+
vscode.l10n.t('The current branch has unpublished commits. Would you like to push your commits before copying a link?'),
227+
{ modal: true },
228+
pushCommits
229+
);
230+
if (selection !== pushCommits) {
231+
throw new vscode.CancellationError();
232+
}
233+
234+
await repository.push();
235+
}
236+
237+
await repository.status();
238+
}

extensions/github/src/shareProviders.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode';
77
import { API } from './typings/git';
88
import { getRepositoryFromUrl, repositoryHasGitHubRemote } from './util';
9-
import { encodeURIComponentExceptSlashes, getRepositoryForFile, notebookCellRangeString, rangeString } from './links';
9+
import { encodeURIComponentExceptSlashes, ensurePublished, getRepositoryForFile, notebookCellRangeString, rangeString } from './links';
1010

1111
export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disposable {
1212
readonly id: string = 'copyVscodeDevLink';
@@ -63,12 +63,14 @@ export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disp
6363
}
6464
}
6565

66-
provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.Uri> {
66+
async provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): Promise<vscode.Uri | undefined> {
6767
const repository = getRepositoryForFile(this.gitAPI, item.resourceUri);
6868
if (!repository) {
6969
return;
7070
}
7171

72+
await ensurePublished(repository, item.resourceUri);
73+
7274
let repo: { owner: string; repo: string } | undefined;
7375
repository.state.remotes.find(remote => {
7476
if (remote.fetchUrl) {

0 commit comments

Comments
 (0)