From 860d67064a9c1ef8ce0c8de35a78bea01033f76c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:59:54 +0100 Subject: [PATCH] Git - refactor ref pickers (#201411) * Refactored quick picks * Another set of refactoring * Moved over the remaining classes * git.checkoutType setting should only be used by the Checkout quick pick * Introduce RefProcessor and cleanup code * Introduced multiple processor types * Another round of refactoring --- extensions/git/package.nls.json | 6 +- extensions/git/src/commands.ts | 558 ++++++++++++++++++++------------ 2 files changed, 351 insertions(+), 213 deletions(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index dc59c3342d7..9b053671d0f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -64,12 +64,12 @@ "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", - "command.merge": "Merge Branch...", + "command.merge": "Merge...", "command.mergeAbort": "Abort Merge", "command.rebase": "Rebase Branch...", "command.createTag": "Create Tag", - "command.deleteTag": "Delete Tag", - "command.deleteRemoteTag": "Delete Remote Tag", + "command.deleteTag": "Delete Tag...", + "command.deleteRemoteTag": "Delete Remote Tag...", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 110c2e20bc6..50a9fbbb22f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -8,7 +8,7 @@ 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, 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'; +import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; import { Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; @@ -20,120 +20,179 @@ import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; import { RemoteSourceAction } from './api/git-base'; -class CheckoutItem implements QuickPickItem { +abstract class CheckoutCommandItem implements QuickPickItem { + abstract get label(): string; + get description(): string { return ''; } + get alwaysShow(): boolean { return true; } +} + +class CreateBranchItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Create new branch...', '$(plus)'); } +} + +class CreateBranchFromItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Create new branch from...', '$(plus)'); } +} + +class CheckoutDetachedItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Checkout detached...', '$(debug-disconnect)'); } +} + +class RefItemSeparator implements QuickPickItem { + get kind(): QuickPickItemKind { return QuickPickItemKind.Separator; } + + get label(): string { + switch (this.refType) { + case RefType.Head: + return l10n.t('branches'); + case RefType.RemoteHead: + return l10n.t('remote branches'); + case RefType.Tag: + return l10n.t('tags'); + default: + return ''; + } + } + + constructor(private readonly refType: RefType) { } +} + +class RefItem implements QuickPickItem { + + get label(): string { + switch (this.ref.type) { + case RefType.Head: + return `$(git-branch) ${this.ref.name ?? this.shortCommit}`; + case RefType.RemoteHead: + return `$(cloud) ${this.ref.name ?? this.shortCommit}`; + case RefType.Tag: + return `$(tag) ${this.ref.name ?? this.shortCommit}`; + default: + return ''; + } + } + + get description(): string { + switch (this.ref.type) { + case RefType.Head: + return this.shortCommit; + case RefType.RemoteHead: + return l10n.t('Remote branch at {0}', this.shortCommit); + case RefType.Tag: + return l10n.t('Tag at {0}', this.shortCommit); + default: + return ''; + } + } - protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - 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 shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } + + private _buttons?: QuickInputButton[]; get buttons(): QuickInputButton[] | undefined { return this._buttons; } set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; } - constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { } + constructor(protected readonly ref: Ref) { } +} - async run(opts?: { detached?: boolean }): Promise { +class CheckoutItem extends RefItem { + + async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); const pullBeforeCheckout = config.get('pullBeforeCheckout', false) === true; const treeish = opts?.detached ? this.ref.commit ?? this.ref.name : this.ref.name; - await this.repository.checkout(treeish, { ...opts, pullBeforeCheckout }); + await repository.checkout(treeish, { ...opts, pullBeforeCheckout }); } } -class CheckoutTagItem extends CheckoutItem { +class CheckoutProtectedItem extends CheckoutItem { - override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; } - override get description(): string { - return l10n.t('Tag at {0}', this.shortCommit); + override get label(): string { + return `$(lock) ${this.ref.name ?? this.shortCommit}`; } - override async run(opts?: { detached?: boolean }): Promise { - if (!this.ref.name) { - return; - } - - await this.repository.checkout(this.ref.name, opts); - } } -class CheckoutRemoteHeadItem extends CheckoutItem { +class CheckoutRemoteHeadItem extends RefItem { - override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; } - override get description(): string { - return l10n.t('Remote branch at {0}', this.shortCommit); - } - - override async run(opts?: { detached?: boolean }): Promise { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } if (opts?.detached) { - await this.repository.checkout(this.ref.commit ?? this.ref.name, opts); + await repository.checkout(this.ref.commit ?? this.ref.name, opts); return; } - const branches = await this.repository.findTrackingBranches(this.ref.name); + const branches = await repository.findTrackingBranches(this.ref.name); if (branches.length > 0) { - await this.repository.checkout(branches[0].name!, opts); + await repository.checkout(branches[0].name!, opts); } else { - await this.repository.checkoutTracking(this.ref.name, opts); + await repository.checkoutTracking(this.ref.name, opts); } } } -class BranchDeleteItem implements QuickPickItem { +class CheckoutTagItem extends RefItem { - private get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get branchName(): string | undefined { return this.ref.name; } - get label(): string { return this.branchName || ''; } - get description(): string { return this.shortCommit; } - - constructor(private ref: Ref) { } - - async run(repository: Repository, force?: boolean): Promise { - if (!this.branchName) { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { + if (!this.ref.name) { return; } - await repository.deleteBranch(this.branchName, force); + + await repository.checkout(this.ref.name, opts); } } -class MergeItem implements QuickPickItem { +class BranchDeleteItem extends RefItem { - private shortCommit: string; - - get label(): string { - return this.ref.type === RefType.RemoteHead ? - `$(cloud) ${this.ref.name ?? this.shortCommit}` : - `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name ?? this.shortCommit}`; - } - - get description(): string { - return this.ref.type === RefType.RemoteHead ? l10n.t('Remote branch at {0}', this.shortCommit) : this.shortCommit; - } - - constructor(private readonly repository: Repository, private readonly ref: Ref) { - this.shortCommit = (this.ref.commit ?? '').substring(0, 8); - } - - async run(): Promise { - await this.repository.merge(this.ref.name ?? this.ref.commit!); + async run(repository: Repository, force?: boolean): Promise { + if (this.ref.name) { + await repository.deleteBranch(this.ref.name, force); + } } } -class RebaseItem implements QuickPickItem { +class TagDeleteItem extends RefItem { - get label(): string { return this.ref.name || ''; } - description: string = ''; + async run(repository: Repository): Promise { + if (this.ref.name) { + await repository.deleteTag(this.ref.name); + } + } +} - constructor(readonly ref: Ref) { } +class RemoteTagDeleteItem extends RefItem { + + override get description(): string { + return l10n.t('Remote tag at {0}', this.shortCommit); + } + + async run(repository: Repository, remote: string): Promise { + if (this.ref.name) { + await repository.deleteRemoteTag(remote, this.ref.name); + } + } +} + +class MergeItem extends RefItem { + + async run(repository: Repository): Promise { + if (this.ref.name || this.ref.commit) { + await repository.merge(this.ref.name ?? this.ref.commit!); + } + } +} + +class RebaseItem extends RefItem { async run(repository: Repository): Promise { if (this.ref?.name) { @@ -142,22 +201,11 @@ class RebaseItem implements QuickPickItem { } } -class CreateBranchItem implements QuickPickItem { - get label(): string { return '$(plus) ' + l10n.t('Create new branch...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } -} +class RebaseUpstreamItem extends RebaseItem { -class CreateBranchFromItem implements QuickPickItem { - get label(): string { return '$(plus) ' + l10n.t('Create new branch from...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } -} - -class CheckoutDetachedItem implements QuickPickItem { - get label(): string { return '$(debug-disconnect) ' + l10n.t('Checkout detached...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } + override get description(): string { + return '(upstream)'; + } } class HEADItem implements QuickPickItem { @@ -265,7 +313,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ return { merge, resolved, unresolved, deletionConflicts }; } -async function createCheckoutItems(repository: Repository, detached = false): Promise { +async function createCheckoutItems(repository: Repository, detached = false): Promise { const config = workspace.getConfiguration('git'); const checkoutTypeConfig = config.get('checkoutType'); let checkoutTypes: string[]; @@ -284,39 +332,13 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr } const refs = await repository.getRefs(); - const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type)) - .filter(p => !!p) as CheckoutProcessor[]; - - for (const ref of refs) { - if (!detached && ref.name === 'origin/HEAD') { - continue; - } - - for (const processor of processors) { - processor.onRef(ref); - } - } + const refProcessors = checkoutTypes.map(type => getCheckoutRefProcessor(repository, type)) + .filter(p => !!p) as RefProcessor[]; 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); - } + const itemsProcessor = new CheckoutItemsProcessor(refProcessors, repository, buttons, detached); - return processors.reduce((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; - })), []); + return itemsProcessor.processRefs(refs); } type RemoteSourceActionButton = { @@ -347,30 +369,181 @@ async function getRemoteRefItemButtons(repository: Repository) { return remoteUrlsToActions; } -class CheckoutProcessor { +class RefProcessor { + protected readonly refs: Ref[] = []; - private refs: Ref[] = []; - get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(this.repository, r)); } - constructor(private repository: Repository, private type: RefType, private ctor: { new(repository: Repository, ref: Ref): CheckoutItem }) { } + get items(): QuickPickItem[] { + const items = this.refs.map(r => new this.ctor(r)); + return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items]; + } - onRef(ref: Ref): void { - if (ref.type === this.type) { - this.refs.push(ref); + constructor(protected readonly type: RefType, protected readonly ctor: { new(ref: Ref): QuickPickItem } = RefItem) { } + + processRef(ref: Ref): boolean { + if (!ref.name && !ref.commit) { + return false; } + if (ref.type !== this.type) { + return false; + } + + this.refs.push(ref); + return true; } } -function getCheckoutProcessor(repository: Repository, type: string): CheckoutProcessor | undefined { - switch (type) { - case 'local': - return new CheckoutProcessor(repository, RefType.Head, CheckoutItem); - case 'remote': - return new CheckoutProcessor(repository, RefType.RemoteHead, CheckoutRemoteHeadItem); - case 'tags': - return new CheckoutProcessor(repository, RefType.Tag, CheckoutTagItem); +class RefItemsProcessor { + + constructor(protected readonly processors: RefProcessor[]) { } + + processRefs(refs: Ref[]): QuickPickItem[] { + for (const ref of refs) { + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + const result: QuickPickItem[] = []; + for (const processor of this.processors) { + result.push(...processor.items); + } + + return result; + } +} + +class RebaseItemsProcessors extends RefItemsProcessor { + + private upstreamName: string | undefined; + + constructor(private readonly repository: Repository) { + super([ + new RefProcessor(RefType.Head, RebaseItem), + new RefProcessor(RefType.RemoteHead, RebaseItem) + ]); + + if (this.repository.HEAD?.upstream) { + this.upstreamName = `${this.repository.HEAD?.upstream.remote}/${this.repository.HEAD?.upstream.name}`; + } } - return undefined; + override processRefs(refs: Ref[]): QuickPickItem[] { + const result: QuickPickItem[] = []; + + for (const ref of refs) { + if (ref.name === this.repository.HEAD?.name) { + continue; + } + + if (ref.name === this.upstreamName) { + result.push(new RebaseUpstreamItem(ref)); + continue; + } + + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + for (const processor of this.processors) { + result.push(...processor.items); + } + + return result; + } +} + +class CheckoutRefProcessor extends RefProcessor { + + override get items(): QuickPickItem[] { + const items = this.refs.map(ref => { + return this.repository.isBranchProtected(ref) ? + new CheckoutProtectedItem(ref) : + new CheckoutItem(ref); + }); + + return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items]; + } + + constructor(private readonly repository: Repository) { + super(RefType.Head); + } +} + +class CheckoutItemsProcessor extends RefItemsProcessor { + + private defaultButtons: RemoteSourceActionButton[] | undefined; + + constructor( + processors: RefProcessor[], + private readonly repository: Repository, + private readonly buttons: Map, + private readonly detached = false) { + super(processors); + + // Default button(s) + 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) { + this.defaultButtons = buttons.get(remoteUrl); + } + } + + override processRefs(refs: Ref[]): QuickPickItem[] { + for (const ref of refs) { + if (!this.detached && ref.name === 'origin/HEAD') { + continue; + } + + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + const result: QuickPickItem[] = []; + for (const processor of this.processors) { + for (const item of processor.items) { + if (!(item instanceof RefItem)) { + result.push(item); + continue; + } + + // Button(s) + if (item.refRemote) { + const matchingRemote = this.repository.remotes.find((remote) => remote.name === item.refRemote); + const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl; + if (remoteUrl) { + item.buttons = this.buttons.get(item.refRemote); + } + } else { + item.buttons = this.defaultButtons; + } + + result.push(item); + } + } + + return result; + } +} + +function getCheckoutRefProcessor(repository: Repository, type: string): RefProcessor | undefined { + switch (type) { + case 'local': + return new CheckoutRefProcessor(repository); + case 'remote': + return new RefProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem); + case 'tags': + return new RefProcessor(RefType.Tag, CheckoutTagItem); + default: + return undefined; + } } function getRepositoryLabel(repositoryRoot: string): string { @@ -391,12 +564,6 @@ function sanitizeRemoteName(name: string) { return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-'); } -class TagItem implements QuickPickItem { - get label(): string { return `$(tag) ${this.ref.name ?? ''}`; } - get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; } - constructor(readonly ref: Ref) { } -} - enum PushType { Push, PushTo, @@ -2230,7 +2397,7 @@ export class CommandCenter { const picks: QuickPickItem[] = []; if (!opts?.detached) { - picks.push(createBranch, createBranchFrom, checkoutDetached, { label: '', kind: QuickPickItemKind.Separator }); + picks.push(createBranch, createBranchFrom, checkoutDetached); } const quickpick = window.createQuickPick(); @@ -2272,7 +2439,7 @@ export class CommandCenter { const item = choice as CheckoutItem; try { - await item.run(opts); + await item.run(repository, opts); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) { throw err; @@ -2285,10 +2452,10 @@ export class CommandCenter { if (choice === force) { await this.cleanAll(repository); - await item.run(opts); + await item.run(repository, opts); } else if (choice === stash || choice === migrate) { if (await this._stash(repository)) { - await item.run(opts); + await item.run(repository, opts); if (choice === migrate) { await this.stashPopLatest(repository); @@ -2408,7 +2575,14 @@ export class CommandCenter { if (from) { const getRefPicks = async () => { - return [new HEADItem(repository), ...await createCheckoutItems(repository)]; + const refs = await repository.getRefs(); + const refProcessors = new RefItemsProcessor([ + new RefProcessor(RefType.Head), + new RefProcessor(RefType.RemoteHead), + new RefProcessor(RefType.Tag) + ]); + + return [new HEADItem(repository), ...refProcessors.processRefs(refs)]; }; const placeHolder = l10n.t('Select a ref to create the branch from'); @@ -2418,7 +2592,7 @@ export class CommandCenter { return; } - if (choice.refName) { + if (choice instanceof RefItem && choice.refName) { target = choice.refName; } } @@ -2448,10 +2622,10 @@ export class CommandCenter { const placeHolder = l10n.t('Select a branch to delete'); const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); - if (!choice || !choice.branchName) { + if (!choice || !choice.refName) { return; } - name = choice.branchName; + name = choice.refName; run = force => choice.run(repository, force); } @@ -2499,32 +2673,23 @@ export class CommandCenter { @command('git.merge', { repository: true }) async merge(repository: Repository): Promise { - const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType'); - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); - - const getBranchPicks = async (): Promise => { + const getQuickPickItems = async (): Promise => { const refs = await repository.getRefs(); + const itemsProcessor = new RefItemsProcessor([ + new RefProcessor(RefType.Head, MergeItem), + new RefProcessor(RefType.RemoteHead, MergeItem), + new RefProcessor(RefType.Tag, MergeItem) + ]); - const heads = refs.filter(ref => ref.type === RefType.Head) - .filter(ref => ref.name || ref.commit) - .map(ref => new MergeItem(repository, ref as Branch)); - - const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit) - .map(ref => new MergeItem(repository, ref as Branch)); - - return [...heads, ...remoteHeads]; + return itemsProcessor.processRefs(refs); }; - const placeHolder = l10n.t('Select a branch to merge from'); - const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); + const placeHolder = l10n.t('Select a branch or tag to merge from'); + const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); - if (!choice) { - return; + if (choice instanceof MergeItem) { + await choice.run(repository); } - - await choice.run(); } @command('git.mergeAbort', { repository: true }) @@ -2534,45 +2699,19 @@ export class CommandCenter { @command('git.rebase', { repository: true }) async rebase(repository: Repository): Promise { - const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType'); - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); - - const getBranchPicks = async () => { + const getQuickPickItems = async (): Promise => { const refs = await repository.getRefs(); + const itemsProcessor = new RebaseItemsProcessors(repository); - const heads = refs.filter(ref => ref.type === RefType.Head) - .filter(ref => ref.name !== repository.HEAD?.name) - .filter(ref => ref.name || ref.commit); - - const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit); - - const picks = [...heads, ...remoteHeads].map(ref => new RebaseItem(ref)); - - // set upstream branch as first - if (repository.HEAD?.upstream) { - const upstreamName = `${repository.HEAD?.upstream.remote}/${repository.HEAD?.upstream.name}`; - const index = picks.findIndex(e => e.ref.name === upstreamName); - - if (index > -1) { - const [ref] = picks.splice(index, 1); - ref.description = '(upstream)'; - picks.unshift(ref); - } - } - - return picks; + return itemsProcessor.processRefs(refs); }; const placeHolder = l10n.t('Select a branch to rebase onto'); - const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); + const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); - if (!choice) { - return; + if (choice instanceof RebaseItem) { + await choice.run(repository); } - - await choice.run(repository); } @command('git.createTag', { repository: true }) @@ -2599,16 +2738,16 @@ export class CommandCenter { @command('git.deleteTag', { repository: true }) async deleteTag(repository: Repository): Promise { - const tagPicks = async (): Promise => { + const tagPicks = async (): Promise => { const remoteTags = await repository.getRefs({ pattern: 'refs/tags' }); - return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagItem(ref)); + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagDeleteItem(ref)); }; const placeHolder = l10n.t('Select a tag to delete'); - const choice = await window.showQuickPick(tagPicks(), { placeHolder }); + const choice = await window.showQuickPick(tagPicks(), { placeHolder }); - if (choice && choice instanceof TagItem && choice.ref.name) { - await repository.deleteTag(choice.ref.name); + if (choice instanceof TagDeleteItem) { + await choice.run(repository); } } @@ -2635,7 +2774,7 @@ export class CommandCenter { remoteName = remotePick.remoteName; } - const remoteTagPicks = async (): Promise => { + const remoteTagPicks = async (): Promise => { const remoteTagsRaw = await repository.getRemoteRefs(remoteName, { tags: true }); // Deduplicate annotated and lightweight tags @@ -2650,14 +2789,14 @@ export class CommandCenter { } } - return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new TagItem(ref)); + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new RemoteTagDeleteItem(ref)); }; - const tagPickPlaceholder = l10n.t('Select a tag to delete'); - const remoteTagPick = await window.showQuickPick(remoteTagPicks(), { placeHolder: tagPickPlaceholder }); + const tagPickPlaceholder = l10n.t('Select a remote tag to delete'); + const remoteTagPick = await window.showQuickPick(remoteTagPicks(), { placeHolder: tagPickPlaceholder }); - if (remoteTagPick && remoteTagPick instanceof TagItem && remoteTagPick.ref.name) { - await repository.deleteRemoteTag(remoteName, remoteTagPick.ref.name); + if (remoteTagPick instanceof RemoteTagDeleteItem) { + await remoteTagPick.run(repository, remoteName); } } @@ -2747,21 +2886,20 @@ export class CommandCenter { remoteName = remotePick.label; } - const getBranchPicks = async (): Promise => { - const remoteRefs = await repository.getRefs(); - const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remoteName)); - return remoteRefsFiltered.map(r => ({ label: r.name! })); + const getBranchPicks = async (): Promise => { + const remoteRefs = await repository.getRefs({ pattern: `refs/remotes/${remoteName}/` }); + return remoteRefs.map(r => new RefItem(r)); }; const branchPlaceHolder = l10n.t('Pick a branch to pull from'); const branchPick = await window.showQuickPick(getBranchPicks(), { placeHolder: branchPlaceHolder }); - if (!branchPick) { + if (!branchPick || !branchPick.refName) { return; } const remoteCharCnt = remoteName.length; - await repository.pullFrom(false, remoteName, branchPick.label.slice(remoteCharCnt + 1)); + await repository.pullFrom(false, remoteName, branchPick.refName.slice(remoteCharCnt + 1)); } @command('git.pull', { repository: true })