mirror of
https://github.com/Microsoft/vscode
synced 2024-10-02 09:18:59 +00:00
Open branches on vscode.dev from ref picker (#181549)
This commit is contained in:
parent
edcad3ab53
commit
9f081fd11a
|
@ -5,9 +5,9 @@
|
|||
|
||||
import { Disposable, commands } from 'vscode';
|
||||
import { Model } from '../model';
|
||||
import { pickRemoteSource } from '../remoteSource';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource';
|
||||
import { GitBaseExtensionImpl } from './extension';
|
||||
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base';
|
||||
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base';
|
||||
|
||||
export class ApiImpl implements API {
|
||||
|
||||
|
@ -17,6 +17,10 @@ export class ApiImpl implements API {
|
|||
return pickRemoteSource(this._model, options as any);
|
||||
}
|
||||
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]> {
|
||||
return getRemoteSourceActions(this._model, url);
|
||||
}
|
||||
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
|
||||
return this._model.registerRemoteSourceProvider(provider);
|
||||
}
|
||||
|
|
10
extensions/git-base/src/api/git-base.d.ts
vendored
10
extensions/git-base/src/api/git-base.d.ts
vendored
|
@ -44,6 +44,15 @@ export interface PickRemoteSourceResult {
|
|||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
|
@ -70,6 +79,7 @@ export interface RemoteSourceProvider {
|
|||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
|
||||
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';
|
||||
import { Model } from './model';
|
||||
import { throttle, debounce } from './decorators';
|
||||
|
||||
|
@ -81,6 +81,20 @@ class RemoteSourceProviderQuickPick {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getRemoteSourceActions(model: Model, url: string): Promise<RemoteSourceAction[]> {
|
||||
const providers = model.getRemoteProviders();
|
||||
|
||||
const remoteSourceActions = [];
|
||||
for (const provider of providers) {
|
||||
const providerActions = await provider.getRemoteSourceActions?.(url);
|
||||
if (providerActions?.length) {
|
||||
remoteSourceActions.push(...providerActions);
|
||||
}
|
||||
}
|
||||
|
||||
return remoteSourceActions;
|
||||
}
|
||||
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
|
|
28
extensions/git/src/api/git-base.d.ts
vendored
28
extensions/git/src/api/git-base.d.ts
vendored
|
@ -8,6 +8,7 @@ export { ProviderResult } from 'vscode';
|
|||
|
||||
export interface API {
|
||||
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
}
|
||||
|
||||
|
@ -31,9 +32,12 @@ export interface GitBaseExtension {
|
|||
|
||||
export interface PickRemoteSourceOptions {
|
||||
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
|
||||
readonly urlLabel?: string;
|
||||
readonly urlLabel?: string | ((url: string) => string);
|
||||
readonly providerName?: string;
|
||||
readonly title?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly branch?: boolean; // then result is PickRemoteSourceResult
|
||||
readonly showRecentSources?: boolean;
|
||||
}
|
||||
|
||||
export interface PickRemoteSourceResult {
|
||||
|
@ -41,20 +45,42 @@ export interface PickRemoteSourceResult {
|
|||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly detail?: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon?: string;
|
||||
readonly url: string | string[];
|
||||
}
|
||||
|
||||
export interface RecentRemoteSource extends RemoteSource {
|
||||
readonly timestamp: number;
|
||||
}
|
||||
|
||||
export interface RemoteSourceProvider {
|
||||
readonly name: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon?: string;
|
||||
readonly label?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind } from 'vscode';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
|
||||
|
@ -17,7 +17,8 @@ import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri';
|
|||
import { grep, isDescendant, pathEquals, relativePath } from './util';
|
||||
import { GitTimelineItem } from './timelineProvider';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { pickRemoteSource } from './remoteSource';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
|
||||
import { RemoteSourceAction } from './api/git-base';
|
||||
|
||||
class CheckoutItem implements QuickPickItem {
|
||||
|
||||
|
@ -25,8 +26,11 @@ class CheckoutItem implements QuickPickItem {
|
|||
get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
get refName(): string | undefined { return this.ref.name; }
|
||||
get refRemote(): string | undefined { return this.ref.remote; }
|
||||
get buttons(): QuickInputButton[] | undefined { return this._buttons; }
|
||||
set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; }
|
||||
|
||||
constructor(protected repository: Repository, protected ref: Ref) { }
|
||||
constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { }
|
||||
|
||||
async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
|
@ -278,7 +282,54 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr
|
|||
}
|
||||
}
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items), []);
|
||||
const buttons = await getRemoteRefItemButtons(repository);
|
||||
let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = [];
|
||||
const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0];
|
||||
const remoteUrl = remote.pushUrl ?? remote.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
fallbackRemoteButtons = buttons.get(remoteUrl);
|
||||
}
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items.map((item) => {
|
||||
if (item.refRemote) {
|
||||
const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote);
|
||||
const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
item.buttons = buttons.get(item.refRemote);
|
||||
}
|
||||
}
|
||||
|
||||
item.buttons = fallbackRemoteButtons;
|
||||
return item;
|
||||
})), []);
|
||||
}
|
||||
|
||||
type RemoteSourceActionButton = {
|
||||
iconPath: ThemeIcon;
|
||||
tooltip: string;
|
||||
actual: RemoteSourceAction;
|
||||
};
|
||||
|
||||
async function getRemoteRefItemButtons(repository: Repository) {
|
||||
// Compute actions for all known remotes
|
||||
const remoteUrlsToActions = new Map<string, RemoteSourceActionButton[]>();
|
||||
|
||||
const getButtons = async (remoteUrl: string) => (await getRemoteSourceActions(remoteUrl)).map((action) => ({ iconPath: new ThemeIcon(action.icon), tooltip: action.label, actual: action }));
|
||||
|
||||
for (const remote of repository.remotes) {
|
||||
if (remote.fetchUrl) {
|
||||
const actions = remoteUrlsToActions.get(remote.fetchUrl) ?? [];
|
||||
actions.push(...await getButtons(remote.fetchUrl));
|
||||
remoteUrlsToActions.set(remote.fetchUrl, actions);
|
||||
}
|
||||
if (remote.pushUrl && remote.pushUrl !== remote.fetchUrl) {
|
||||
const actions = remoteUrlsToActions.get(remote.pushUrl) ?? [];
|
||||
actions.push(...await getButtons(remote.pushUrl));
|
||||
remoteUrlsToActions.set(remote.pushUrl, actions);
|
||||
}
|
||||
}
|
||||
|
||||
return remoteUrlsToActions;
|
||||
}
|
||||
|
||||
class CheckoutProcessor {
|
||||
|
@ -2084,7 +2135,17 @@ export class CommandCenter {
|
|||
quickpick.items = picks;
|
||||
quickpick.busy = false;
|
||||
|
||||
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
|
||||
const choice = await new Promise<QuickPickItem | undefined>(c => {
|
||||
quickpick.onDidAccept(() => c(quickpick.activeItems[0]));
|
||||
quickpick.onDidTriggerItemButton((e) => {
|
||||
quickpick.hide();
|
||||
const button = e.button as QuickInputButton & { actual: RemoteSourceAction };
|
||||
const item = e.item as CheckoutItem;
|
||||
if (button.actual && item.refName) {
|
||||
button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName);
|
||||
}
|
||||
});
|
||||
});
|
||||
quickpick.hide();
|
||||
|
||||
if (!choice) {
|
||||
|
|
|
@ -11,3 +11,7 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions & { bran
|
|||
export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
return GitBaseApi.getAPI().pickRemoteSource(options);
|
||||
}
|
||||
|
||||
export async function getRemoteSourceActions(url: string) {
|
||||
return GitBaseApi.getAPI().getRemoteSourceActions(url);
|
||||
}
|
||||
|
|
|
@ -7,11 +7,7 @@ import * as vscode from 'vscode';
|
|||
import { API as GitAPI } from './typings/git';
|
||||
import { publishRepository } from './publish';
|
||||
import { DisposableStore } from './util';
|
||||
import { LinkContext, getLink } from './links';
|
||||
|
||||
function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
import { LinkContext, getLink, getVscodeDevHost } from './links';
|
||||
|
||||
async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) {
|
||||
try {
|
||||
|
|
|
@ -168,3 +168,17 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri
|
|||
return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment
|
||||
}${fileSegments}`;
|
||||
}
|
||||
|
||||
export function getBranchLink(url: string, branch: string, hostPrefix: string = 'https://github.com') {
|
||||
const repo = getRepositoryFromUrl(url);
|
||||
if (!repo) {
|
||||
throw new Error('Invalid repository URL provided');
|
||||
}
|
||||
|
||||
branch = encodeURIComponentExceptSlashes(branch);
|
||||
return `${hostPrefix}/${repo.owner}/${repo.repo}/tree/${branch}`;
|
||||
}
|
||||
|
||||
export function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource } from './typings/git-base';
|
||||
import { Uri, env, l10n, workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, RemoteSourceAction } from './typings/git-base';
|
||||
import { getOctokit } from './auth';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl } from './util';
|
||||
import { getBranchLink, getVscodeDevHost } from './links';
|
||||
|
||||
function asRemoteSource(raw: any): RemoteSource {
|
||||
const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol');
|
||||
|
@ -112,4 +113,27 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
|
|||
|
||||
return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0);
|
||||
}
|
||||
|
||||
async getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]> {
|
||||
const repository = getRepositoryFromUrl(url);
|
||||
if (!repository) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{
|
||||
label: l10n.t('Open on GitHub'),
|
||||
icon: 'github',
|
||||
run(branch: string) {
|
||||
const link = getBranchLink(url, branch);
|
||||
env.openExternal(Uri.parse(link));
|
||||
}
|
||||
}, {
|
||||
label: l10n.t('Checkout on vscode.dev'),
|
||||
icon: 'globe',
|
||||
run(branch: string) {
|
||||
const link = getBranchLink(url, branch, getVscodeDevHost());
|
||||
env.openExternal(Uri.parse(link));
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
10
extensions/github/src/typings/git-base.d.ts
vendored
10
extensions/github/src/typings/git-base.d.ts
vendored
|
@ -44,6 +44,15 @@ export interface PickRemoteSourceResult {
|
|||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export interface RemoteSourceAction {
|
||||
readonly label: string;
|
||||
/**
|
||||
* Codicon name
|
||||
*/
|
||||
readonly icon: string;
|
||||
run(branch: string): void;
|
||||
}
|
||||
|
||||
export interface RemoteSource {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
|
@ -70,6 +79,7 @@ export interface RemoteSourceProvider {
|
|||
readonly supportsQuery?: boolean;
|
||||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue