[#25575] Add Git Tag feature.

This commit is contained in:
Ashutosh Dhundhara 2017-05-20 22:58:00 +05:30
parent 56b341c2e0
commit 211e7a3030
5 changed files with 158 additions and 6 deletions

View file

@ -172,6 +172,21 @@
"title": "%command.branch%",
"category": "Git"
},
{
"command": "git.createTag",
"title": "%command.createTag%",
"category": "Git"
},
{
"command": "git.showTags",
"title": "%command.showTags%",
"category": "Git"
},
{
"command": "git.pushWithTags",
"title": "%command.pushWithTags%",
"category": "Git"
},
{
"command": "git.pull",
"title": "%command.pull%",
@ -306,10 +321,22 @@
"command": "git.pullRebase",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
},
{
"command": "git.showTags",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
},
{
"command": "git.createTag",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
},
{
"command": "git.push",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
},
{
"command": "git.pushWithTags",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
},
{
"command": "git.pushTo",
"when": "config.git.enabled && scmProvider == git && gitState == idle"

View file

@ -21,8 +21,11 @@
"command.undoCommit": "Undo Last Commit",
"command.checkout": "Checkout to...",
"command.branch": "Create Branch...",
"command.createTag": "Create Tag",
"command.showTags": "Show Tags",
"command.pull": "Pull",
"command.pullRebase": "Pull (Rebase)",
"command.pushWithTags": "Push With Tags",
"command.push": "Push",
"command.pushTo": "Push to...",
"command.sync": "Sync",

View file

@ -6,7 +6,7 @@
'use strict';
import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState } from 'vscode';
import { Ref, RefType, Git, GitErrorCodes } from './git';
import { Ref, RefType, Git, GitErrorCodes, PushOptions } from './git';
import { Model, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './model';
import { toGitUri, fromGitUri } from './uri';
import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging';
@ -17,6 +17,10 @@ import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class PushOptionsImpl implements PushOptions {
withTags: boolean;
}
class CheckoutItem implements QuickPickItem {
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
@ -37,6 +41,26 @@ class CheckoutItem implements QuickPickItem {
}
}
class ShowTag implements QuickPickItem {
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
get label(): string { return this.ref.name || this.shortCommit; }
get description(): string { return this.shortCommit; }
constructor(protected ref: Ref) { }
async run(model: Model): Promise<void> {
const result = await model.showObject(this.ref.name || '');
if (!result) {
return;
}
workspace.openTextDocument({ language: 'en', content: result.trim() })
.then(window.showTextDocument);
}
}
class CheckoutTagItem extends CheckoutItem {
get description(): string {
@ -699,6 +723,48 @@ export class CommandCenter {
await this.model.branch(name);
}
@command('git.showTags')
async showTags(): Promise<void> {
const tags = this.model.refs
.filter(x => x.type === RefType.Tag)
.map(tag => new ShowTag(tag));
const placeHolder = 'Select a tag';
var choice = await window.showQuickPick<ShowTag>(tags, { placeHolder });
if (!choice) {
return;
}
await choice.run(this.model);
}
@command('git.createTag')
async createTag(): Promise<void> {
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) {
return;
}
const inputMessage = await window.showInputBox({
placeHolder: localize('tag message', "Message"),
prompt: localize('provide tag message', "Please provide a message"),
ignoreFocusOut: true
});
const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
const message = inputMessage || name;
await this.model.tag(name, message);
window.showInformationMessage(localize('tag creation success', "Successfully created tag."));
}
@command('git.pull')
async pull(): Promise<void> {
const remotes = this.model.remotes;
@ -735,6 +801,23 @@ export class CommandCenter {
await this.model.push();
}
@command('git.pushWithTags')
async pushWithTags(): Promise<void> {
const remotes = this.model.remotes;
if (remotes.length === 0) {
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
return;
}
let pushOptions = new PushOptionsImpl();
pushOptions.withTags = true;
await this.model.push(undefined, undefined, pushOptions);
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
}
@command('git.pushTo')
async pushTo(): Promise<void> {
const remotes = this.model.remotes;

View file

@ -23,6 +23,7 @@ export interface IGit {
export interface PushOptions {
setUpstream?: boolean;
withTags?: boolean;
}
export interface IFileStatus {
@ -650,6 +651,29 @@ export class Repository {
await this.run(args);
}
async show(ref: string): Promise<string> {
let args = ['show', '-s', '--format=%H\n%B', ref];
const result = await this.run(args);
if (!result) {
return Promise.reject<string>('Invalid reference provided.');
}
return result.stdout;
}
async tag(name: string, message: string, lightweight: boolean): Promise<void> {
let args = ['tag'];
if (lightweight) {
args.push(name);
} else {
args = args.concat(['-a', name, '-m', message]);
}
await this.run(args);
}
async clean(paths: string[]): Promise<void> {
const pathsByGroup = groupBy(paths, p => path.dirname(p));
const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]);
@ -757,8 +781,14 @@ export class Repository {
async push(remote?: string, name?: string, options?: PushOptions): Promise<void> {
const args = ['push'];
if (options && options.setUpstream) {
args.push('-u');
if (options) {
if (options.setUpstream) {
args.push('-u');
}
if (options.withTags) {
args.push('--tags');
}
}
if (remote) {
@ -945,8 +975,8 @@ export class Repository {
}
async getCommit(ref: string): Promise<Commit> {
const result = await this.run(['show', '-s', '--format=%H\n%B', ref]);
const match = /^([0-9a-f]{40})\n([^]*)$/m.exec(result.stdout.trim());
const result = await this.show(ref);
const match = /^([0-9a-f]{40})\n([^]*)$/m.exec(result.trim());
if (!match) {
return Promise.reject<Commit>('bad commit format');

View file

@ -211,7 +211,8 @@ export enum Operation {
Init = 1 << 12,
Show = 1 << 13,
Stage = 1 << 14,
GetCommitTemplate = 1 << 15
GetCommitTemplate = 1 << 15,
Tag = 1 << 16
}
// function getOperationName(operation: Operation): string {
@ -454,6 +455,10 @@ export class Model implements Disposable {
await this.run(Operation.Branch, () => this.repository.branch(name, true));
}
async tag(name: string, message: string): Promise<void> {
await this.run(Operation.Tag, () => this.repository.tag(name, message, false));
}
async checkout(treeish: string): Promise<void> {
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
}
@ -506,6 +511,10 @@ export class Model implements Disposable {
});
}
async showObject(ref: string): Promise<string> {
return await this.run(Operation.Show, () => this.repository.show(ref));
}
async getCommitTemplate(): Promise<string> {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}