77
88import { Uri , EventEmitter , Event , SCMResource , SCMResourceDecorations , SCMResourceGroup } from 'vscode' ;
99import { Repository , IRef , IBranch , IRemote , IPushOptions } from './git' ;
10- import { throttle } from './util' ;
11- import { decorate } from 'core-decorators' ;
10+ import { throttle , anyEvent } from './util' ;
11+ import { decorate , memoize } from 'core-decorators' ;
1212import * as path from 'path' ;
1313
1414const iconsRootPath = path . join ( path . dirname ( __dirname ) , 'resources' , 'icons' ) ;
@@ -150,11 +150,58 @@ export class WorkingTreeGroup extends ResourceGroup {
150150 }
151151}
152152
153+ export enum Operation {
154+ Status = 0o1 ,
155+ Stage = 0o2 ,
156+ Unstage = 0o4 ,
157+ Commit = 0o10 ,
158+ Clean = 0o20 ,
159+ Branch = 0o40 ,
160+ Checkout = 0o100 ,
161+ Fetch = 0o200 ,
162+ Sync = 0o400 ,
163+ Push = 0o1000
164+ }
165+
166+ export interface Operations {
167+ isRunning ( operation : Operation ) : boolean ;
168+ }
169+
170+ class OperationsImpl implements Operations {
171+
172+ constructor ( private readonly operations : number = 0 ) {
173+ // noop
174+ }
175+
176+ start ( operation : Operation ) : OperationsImpl {
177+ return new OperationsImpl ( this . operations | operation ) ;
178+ }
179+
180+ end ( operation : Operation ) : OperationsImpl {
181+ return new OperationsImpl ( this . operations & ~ operation ) ;
182+ }
183+
184+ isRunning ( operation : Operation ) : boolean {
185+ return ( this . operations & operation ) !== 0 ;
186+ }
187+ }
188+
153189export class Model {
154190
155191 private _onDidChange = new EventEmitter < SCMResourceGroup [ ] > ( ) ;
156192 readonly onDidChange : Event < SCMResourceGroup [ ] > = this . _onDidChange . event ;
157193
194+ private _onRunOperation = new EventEmitter < Operation > ( ) ;
195+ readonly onRunOperation : Event < Operation > = this . _onRunOperation . event ;
196+
197+ private _onDidRunOperation = new EventEmitter < Operation > ( ) ;
198+ readonly onDidRunOperation : Event < Operation > = this . _onDidRunOperation . event ;
199+
200+ @memoize
201+ get onDidChangeOperations ( ) : Event < void > {
202+ return anyEvent ( this . onRunOperation as Event < any > , this . onDidRunOperation as Event < any > ) ;
203+ }
204+
158205 private _mergeGroup = new MergeGroup ( [ ] ) ;
159206 get mergeGroup ( ) : MergeGroup { return this . _mergeGroup ; }
160207
@@ -180,6 +227,9 @@ export class Model {
180227 return result ;
181228 }
182229
230+ private _operations = new OperationsImpl ( ) ;
231+ get operations ( ) : Operations { return this . _operations ; }
232+
183233 constructor ( private _repositoryRoot : string , private repository : Repository ) {
184234
185235 }
@@ -205,145 +255,186 @@ export class Model {
205255
206256 @decorate ( throttle )
207257 async update ( ) : Promise < void > {
208- const status = await this . repository . getStatus ( ) ;
209- let HEAD : IBranch | undefined ;
210-
211- try {
212- HEAD = await this . repository . getHEAD ( ) ;
213-
214- if ( HEAD . name ) {
215- try {
216- HEAD = await this . repository . getBranch ( HEAD . name ) ;
217- } catch ( err ) {
218- // noop
258+ await this . run ( Operation . Status , async ( ) => {
259+ const status = await this . repository . getStatus ( ) ;
260+ let HEAD : IBranch | undefined ;
261+
262+ try {
263+ HEAD = await this . repository . getHEAD ( ) ;
264+
265+ if ( HEAD . name ) {
266+ try {
267+ HEAD = await this . repository . getBranch ( HEAD . name ) ;
268+ } catch ( err ) {
269+ // noop
270+ }
219271 }
272+ } catch ( err ) {
273+ // noop
220274 }
221- } catch ( err ) {
222- // noop
223- }
224275
225- const [ refs , remotes ] = await Promise . all ( [ this . repository . getRefs ( ) , this . repository . getRemotes ( ) ] ) ;
226-
227- this . _HEAD = HEAD ;
228- this . _refs = refs ;
229- this . _remotes = remotes ;
230-
231- const index : Resource [ ] = [ ] ;
232- const workingTree : Resource [ ] = [ ] ;
233- const merge : Resource [ ] = [ ] ;
234-
235- status . forEach ( raw => {
236- const uri = Uri . file ( path . join ( this . repositoryRoot , raw . path ) ) ;
237-
238- switch ( raw . x + raw . y ) {
239- case '??' : return workingTree . push ( new Resource ( uri , Status . UNTRACKED ) ) ;
240- case '!!' : return workingTree . push ( new Resource ( uri , Status . IGNORED ) ) ;
241- case 'DD' : return merge . push ( new Resource ( uri , Status . BOTH_DELETED ) ) ;
242- case 'AU' : return merge . push ( new Resource ( uri , Status . ADDED_BY_US ) ) ;
243- case 'UD' : return merge . push ( new Resource ( uri , Status . DELETED_BY_THEM ) ) ;
244- case 'UA' : return merge . push ( new Resource ( uri , Status . ADDED_BY_THEM ) ) ;
245- case 'DU' : return merge . push ( new Resource ( uri , Status . DELETED_BY_US ) ) ;
246- case 'AA' : return merge . push ( new Resource ( uri , Status . BOTH_ADDED ) ) ;
247- case 'UU' : return merge . push ( new Resource ( uri , Status . BOTH_MODIFIED ) ) ;
248- }
276+ const [ refs , remotes ] = await Promise . all ( [ this . repository . getRefs ( ) , this . repository . getRemotes ( ) ] ) ;
277+
278+ this . _HEAD = HEAD ;
279+ this . _refs = refs ;
280+ this . _remotes = remotes ;
281+
282+ const index : Resource [ ] = [ ] ;
283+ const workingTree : Resource [ ] = [ ] ;
284+ const merge : Resource [ ] = [ ] ;
285+
286+ status . forEach ( raw => {
287+ const uri = Uri . file ( path . join ( this . repositoryRoot , raw . path ) ) ;
288+
289+ switch ( raw . x + raw . y ) {
290+ case '??' : return workingTree . push ( new Resource ( uri , Status . UNTRACKED ) ) ;
291+ case '!!' : return workingTree . push ( new Resource ( uri , Status . IGNORED ) ) ;
292+ case 'DD' : return merge . push ( new Resource ( uri , Status . BOTH_DELETED ) ) ;
293+ case 'AU' : return merge . push ( new Resource ( uri , Status . ADDED_BY_US ) ) ;
294+ case 'UD' : return merge . push ( new Resource ( uri , Status . DELETED_BY_THEM ) ) ;
295+ case 'UA' : return merge . push ( new Resource ( uri , Status . ADDED_BY_THEM ) ) ;
296+ case 'DU' : return merge . push ( new Resource ( uri , Status . DELETED_BY_US ) ) ;
297+ case 'AA' : return merge . push ( new Resource ( uri , Status . BOTH_ADDED ) ) ;
298+ case 'UU' : return merge . push ( new Resource ( uri , Status . BOTH_MODIFIED ) ) ;
299+ }
249300
250- let isModifiedInIndex = false ;
301+ let isModifiedInIndex = false ;
251302
252- switch ( raw . x ) {
253- case 'M' : index . push ( new Resource ( uri , Status . INDEX_MODIFIED ) ) ; isModifiedInIndex = true ; break ;
254- case 'A' : index . push ( new Resource ( uri , Status . INDEX_ADDED ) ) ; break ;
255- case 'D' : index . push ( new Resource ( uri , Status . INDEX_DELETED ) ) ; break ;
256- case 'R' : index . push ( new Resource ( uri , Status . INDEX_RENAMED /*, raw.rename*/ ) ) ; break ;
257- case 'C' : index . push ( new Resource ( uri , Status . INDEX_COPIED ) ) ; break ;
258- }
303+ switch ( raw . x ) {
304+ case 'M' : index . push ( new Resource ( uri , Status . INDEX_MODIFIED ) ) ; isModifiedInIndex = true ; break ;
305+ case 'A' : index . push ( new Resource ( uri , Status . INDEX_ADDED ) ) ; break ;
306+ case 'D' : index . push ( new Resource ( uri , Status . INDEX_DELETED ) ) ; break ;
307+ case 'R' : index . push ( new Resource ( uri , Status . INDEX_RENAMED /*, raw.rename*/ ) ) ; break ;
308+ case 'C' : index . push ( new Resource ( uri , Status . INDEX_COPIED ) ) ; break ;
309+ }
259310
260- switch ( raw . y ) {
261- case 'M' : workingTree . push ( new Resource ( uri , Status . MODIFIED /*, raw.rename*/ ) ) ; break ;
262- case 'D' : workingTree . push ( new Resource ( uri , Status . DELETED /*, raw.rename*/ ) ) ; break ;
263- }
264- } ) ;
311+ switch ( raw . y ) {
312+ case 'M' : workingTree . push ( new Resource ( uri , Status . MODIFIED /*, raw.rename*/ ) ) ; break ;
313+ case 'D' : workingTree . push ( new Resource ( uri , Status . DELETED /*, raw.rename*/ ) ) ; break ;
314+ }
315+ } ) ;
265316
266- this . _mergeGroup = new MergeGroup ( merge ) ;
267- this . _indexGroup = new IndexGroup ( index ) ;
268- this . _workingTreeGroup = new WorkingTreeGroup ( workingTree ) ;
317+ this . _mergeGroup = new MergeGroup ( merge ) ;
318+ this . _indexGroup = new IndexGroup ( index ) ;
319+ this . _workingTreeGroup = new WorkingTreeGroup ( workingTree ) ;
269320
270- this . _onDidChange . fire ( this . resources ) ;
321+ this . _onDidChange . fire ( this . resources ) ;
322+ } ) ;
271323 }
272324
325+ @decorate ( throttle )
273326 async stage ( ...resources : Resource [ ] ) : Promise < void > {
274- const paths = resources . map ( r => r . uri . fsPath ) ;
275- await this . repository . add ( paths ) ;
276- await this . update ( ) ;
327+ await this . run ( Operation . Stage , async ( ) => {
328+ const paths = resources . map ( r => r . uri . fsPath ) ;
329+ await this . repository . add ( paths ) ;
330+ await this . update ( ) ;
331+ } ) ;
277332 }
278333
334+ @decorate ( throttle )
279335 async unstage ( ...resources : Resource [ ] ) : Promise < void > {
280- const paths = resources . map ( r => r . uri . fsPath ) ;
281- await this . repository . revertFiles ( 'HEAD' , paths ) ;
282- await this . update ( ) ;
336+ await this . run ( Operation . Unstage , async ( ) => {
337+ const paths = resources . map ( r => r . uri . fsPath ) ;
338+ await this . repository . revertFiles ( 'HEAD' , paths ) ;
339+ await this . update ( ) ;
340+ } ) ;
283341 }
284342
343+ @decorate ( throttle )
285344 async commit ( message : string , opts : { all ?: boolean , amend ?: boolean , signoff ?: boolean } = Object . create ( null ) ) : Promise < void > {
286- if ( opts . all ) {
287- await this . repository . add ( [ ] ) ;
288- }
345+ await this . run ( Operation . Commit , async ( ) => {
346+ if ( opts . all ) {
347+ await this . repository . add ( [ ] ) ;
348+ }
289349
290- await this . repository . commit ( message , opts ) ;
291- await this . update ( ) ;
350+ await this . repository . commit ( message , opts ) ;
351+ await this . update ( ) ;
352+ } ) ;
292353 }
293354
355+ @decorate ( throttle )
294356 async clean ( ...resources : Resource [ ] ) : Promise < void > {
295- const toClean : string [ ] = [ ] ;
296- const toCheckout : string [ ] = [ ] ;
297-
298- resources . forEach ( r => {
299- switch ( r . type ) {
300- case Status . UNTRACKED :
301- case Status . IGNORED :
302- toClean . push ( r . uri . fsPath ) ;
303- break ;
304-
305- default :
306- toCheckout . push ( r . uri . fsPath ) ;
307- break ;
308- }
309- } ) ;
357+ await this . run ( Operation . Clean , async ( ) => {
358+ const toClean : string [ ] = [ ] ;
359+ const toCheckout : string [ ] = [ ] ;
360+
361+ resources . forEach ( r => {
362+ switch ( r . type ) {
363+ case Status . UNTRACKED :
364+ case Status . IGNORED :
365+ toClean . push ( r . uri . fsPath ) ;
366+ break ;
367+
368+ default :
369+ toCheckout . push ( r . uri . fsPath ) ;
370+ break ;
371+ }
372+ } ) ;
310373
311- const promises : Promise < void > [ ] = [ ] ;
374+ const promises : Promise < void > [ ] = [ ] ;
312375
313- if ( toClean . length > 0 ) {
314- promises . push ( this . repository . clean ( toClean ) ) ;
315- }
376+ if ( toClean . length > 0 ) {
377+ promises . push ( this . repository . clean ( toClean ) ) ;
378+ }
316379
317- if ( toCheckout . length > 0 ) {
318- promises . push ( this . repository . checkout ( '' , toCheckout ) ) ;
319- }
380+ if ( toCheckout . length > 0 ) {
381+ promises . push ( this . repository . checkout ( '' , toCheckout ) ) ;
382+ }
320383
321- await Promise . all ( promises ) ;
322- await this . update ( ) ;
384+ await Promise . all ( promises ) ;
385+ await this . update ( ) ;
386+ } ) ;
323387 }
324388
389+ @decorate ( throttle )
325390 async branch ( name : string ) : Promise < void > {
326- await this . repository . branch ( name , true ) ;
327- await this . update ( ) ;
391+ await this . run ( Operation . Branch , async ( ) => {
392+ await this . repository . branch ( name , true ) ;
393+ await this . update ( ) ;
394+ } ) ;
328395 }
329396
397+ @decorate ( throttle )
330398 async checkout ( treeish : string ) : Promise < void > {
331- await this . repository . checkout ( treeish , [ ] ) ;
332- await this . update ( ) ;
399+ await this . run ( Operation . Checkout , async ( ) => {
400+ await this . repository . checkout ( treeish , [ ] ) ;
401+ await this . update ( ) ;
402+ } ) ;
333403 }
334404
405+ @decorate ( throttle )
335406 async fetch ( ) : Promise < void > {
336- await this . repository . fetch ( ) ;
337- await this . update ( ) ;
407+ await this . run ( Operation . Fetch , async ( ) => {
408+ await this . repository . fetch ( ) ;
409+ await this . update ( ) ;
410+ } ) ;
338411 }
339412
413+ @decorate ( throttle )
340414 async sync ( ) : Promise < void > {
341- await this . repository . sync ( ) ;
342- await this . update ( ) ;
415+ await this . run ( Operation . Sync , async ( ) => {
416+ await this . repository . sync ( ) ;
417+ await this . update ( ) ;
418+ } ) ;
343419 }
344420
421+ @decorate ( throttle )
345422 async push ( remote ?: string , name ?: string , options ?: IPushOptions ) : Promise < void > {
346- await this . repository . push ( remote , name , options ) ;
347- await this . update ( ) ;
423+ await this . run ( Operation . Push , async ( ) => {
424+ await this . repository . push ( remote , name , options ) ;
425+ await this . update ( ) ;
426+ } ) ;
427+ }
428+
429+ private async run ( operation : Operation , fn : ( ) => Promise < void > ) : Promise < void > {
430+ this . _operations = this . _operations . start ( operation ) ;
431+ this . _onRunOperation . fire ( operation ) ;
432+
433+ try {
434+ await fn ( ) ;
435+ } finally {
436+ this . _operations = this . _operations . end ( operation ) ;
437+ this . _onDidRunOperation . fire ( operation ) ;
438+ }
348439 }
349440}
0 commit comments