diff --git a/extensions/git/package.json b/extensions/git/package.json index 70a4c5fe0f3..4f71c451c24 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -272,6 +272,11 @@ "title": "%command.deleteTag%", "category": "Git" }, + { + "command": "git.getTags", + "title": "%command.getTags%", + "category": "Git" + }, { "command": "git.fetch", "title": "%command.fetch%", @@ -564,6 +569,10 @@ "command": "git.deleteTag", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.getTags", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.fetch", "when": "config.git.enabled && gitOpenRepositoryCount != 0" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 4a1c1557cab..ba28d15cb31 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -38,6 +38,7 @@ "command.merge": "Merge Branch...", "command.createTag": "Create Tag", "command.deleteTag": "Delete Tag", + "command.getTags": "Get Tags", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 21195974fc1..9cba9617103 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -37,6 +37,11 @@ export interface Branch extends Ref { readonly behind?: number; } +export interface Tag extends Ref { + readonly name: string; + readonly message?: string; +} + export interface Commit { readonly hash: string; readonly message: string; diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9e971e8e793..df84395bd7c 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -15,7 +15,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; -import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git'; +import { Ref, RefType, Branch, GitErrorCodes, Status, Tag } from './api/git'; const localize = nls.loadMessageBundle(); @@ -38,6 +38,25 @@ class CheckoutItem implements QuickPickItem { } } +class TagItem implements QuickPickItem { + + get label(): string { return (this.tag.name || '').substr(0, 20); } + get name(): string { return (this.tag.name || ''); } + get description(): string { + return (this.tag.message || ''); + } + constructor(protected tag: Tag) { } + + async run(repository: Repository): Promise { + const name = this.tag.name || ''; + if (!name) { + return; + } + + await repository.deleteTag(name); + } +} + class CheckoutTagItem extends CheckoutItem { get description(): string { @@ -199,6 +218,11 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { return [...heads, ...tags, ...remoteHeads]; } +async function createTagItems(repository: Repository): Promise { + const tags = await repository.getTags(); + return tags.map(tag => new TagItem(tag)) || []; +} + enum PushType { Push, PushTo, @@ -1667,18 +1691,16 @@ export class CommandCenter { @command('git.deleteTag', { repository: true }) async deleteTag(repository: Repository): Promise { - const inputTagName = await window.showInputBox({ - placeHolder: localize('tag name', "Tag name"), - prompt: localize('provide tag name', "Please provide a tag name"), - ignoreFocusOut: true - }); - - if (!inputTagName) { + const picks = await createTagItems(repository); + if (!picks) { + window.showWarningMessage(localize('no tags', "This repository has no tags.")); + } + const placeHolder = localize('select a tag to delete', 'Select a tag to delete'); + const choice = await window.showQuickPick(picks, { placeHolder }); + if (!choice) { return; } - - const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); - await repository.deleteTag(name); + await repository.deleteTag(choice.name); } @command('git.fetch', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index e14ce04342c..7fd63a40818 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -15,7 +15,7 @@ import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, import { CancellationToken } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; -import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; +import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status, Tag } from './api/git'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; @@ -1290,6 +1290,21 @@ export class Repository { await this.run(args); } + async getTags(): Promise { + let args = ['tag', '-n1']; + const result = await this.run(args); + return result.stdout.trim().split('\n') + .map(line => line.trim().split('\0')) + .map(([line]) => { + const name = line.split(' ')[0]; + return { + name: name, + message: line.replace(name, '').trim() || '', + type: RefType.Tag + } as Tag; + }); + } + async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths, p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b48d968c558..b0372add8df 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -13,7 +13,7 @@ import * as path from 'path'; import * as nls from 'vscode-nls'; import * as fs from 'fs'; import { StatusBarCommands } from './statusbar'; -import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git'; +import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change, Tag } from './api/git'; import { IFileWatcher, watch } from './watch'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -296,6 +296,7 @@ export const enum Operation { Ignore = 'Ignore', Tag = 'Tag', DeleteTag = 'DeleteTag', + GetTags = 'GetTags', Stash = 'Stash', CheckIgnore = 'CheckIgnore', GetObjectDetails = 'GetObjectDetails', @@ -1006,6 +1007,10 @@ export class Repository implements Disposable { await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } + async getTags(): Promise { + return await this.run(Operation.GetTags, () => this.repository.getTags()); + } + async checkout(treeish: string): Promise { await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [])); }