diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index b607a5fb2cb8d25b9eee351851f57906a83e02be..ec59d474d34107afcde5fa24daeb313c6beae7b9 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -33,6 +33,11 @@ export interface Remote { url: string; } +export interface Stash { + id : string; + description: string; +} + export enum RefType { Head, RemoteHead, @@ -277,7 +282,10 @@ export const GitErrorCodes = { RepositoryNotFound: 'RepositoryNotFound', RepositoryIsLocked: 'RepositoryIsLocked', BranchNotFullyMerged: 'BranchNotFullyMerged', - NoRemoteReference: 'NoRemoteReference' + NoRemoteReference: 'NoRemoteReference', + NoLocalChanges: 'NoLocalChanges', + NoStashFound: 'NoStashFound', + LocalChangesOverwritten: 'LocalChangesOverwritten' }; function getGitErrorCode(stderr: string): string | undefined { @@ -834,6 +842,32 @@ export class Repository { } } + async stash(pop: boolean = false, index?: string): Promise { + try { + const args = ['stash']; + + if (pop) { + args.push('pop'); + if (index) { + args.push(`"stash{${index}}"`); + } + } + + await this.run(args); + } catch (err) { + if (/No local changes to save/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.NoLocalChanges; + } + else if (/No stash found/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.NoStashFound; + } + else if (/error: Your local changes to the following files would be overwritten/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.LocalChangesOverwritten; + } + throw err; + } + } + getStatus(limit = 5000): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> { return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => { const parser = new GitStatusParser(); @@ -921,6 +955,18 @@ export class Repository { .filter(ref => !!ref) as Ref[]; } + async getStashes(): Promise { + const result = await this.run(['stash', 'list']); + const regex = /^stash@{(\d+)}:(.+)/; + const rawStashes = result.stdout.trim().split('\n') + .filter(b => !!b) + .map(line => regex.exec(line)) + .filter(g => !!g) + .map((groups: RegExpExecArray) => ({ id: groups[1], description: groups[2] })); + return uniqBy(rawStashes, remote => remote.id); + } + + async getRemotes(): Promise { const result = await this.run(['remote', '--verbose']); const regex = /^([^\s]+)\s+([^\s]+)\s/; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 3f0177dde7d045c9332a7b0a1624c679820e39fa..553938f29f80c91a2b3247f470521e72d2590871 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,7 @@ 'use strict'; import { Uri, Command, EventEmitter, Event, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; -import { Git, Repository, Ref, Branch, Remote, Commit, GitErrorCodes } from './git'; +import { Git, Repository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose } from './util'; import { memoize, throttle, debounce } from './decorators'; import * as path from 'path'; @@ -215,7 +215,8 @@ export enum Operation { DeleteBranch = 1 << 16, Merge = 1 << 17, Ignore = 1 << 18, - Tag = 1 << 19 + Tag = 1 << 19, + Stash = 1 << 20 } // function getOperationName(operation: Operation): string { @@ -345,6 +346,11 @@ export class Model implements Disposable { return this._remotes; } + private _stashes: Stash[] = []; + get stashes(): Stash[] { + return this._stashes; + } + private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } @@ -359,6 +365,7 @@ export class Model implements Disposable { this._HEAD = undefined; this._refs = []; this._remotes = []; + this._stashes = []; this._mergeGroup = new MergeGroup(); this._indexGroup = new IndexGroup(); this._workingTreeGroup = new WorkingTreeGroup(); @@ -542,6 +549,11 @@ export class Model implements Disposable { }); } + @throttle + async stash(pop: boolean = false, index?: string): Promise { + return await this.run(Operation.Stash, () => this.repository.stash(pop, index)); + } + async getCommitTemplate(): Promise { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } @@ -692,11 +704,12 @@ export class Model implements Disposable { // noop } - const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]); + const [refs, remotes, stashes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getStashes()]); this._HEAD = HEAD; this._refs = refs; this._remotes = remotes; + this._stashes = stashes; const index: Resource[] = []; const workingTree: Resource[] = [];