77
88import { Uri , EventEmitter , Event , SCMResource , SCMResourceDecorations , SCMResourceGroup , Disposable } from 'vscode' ;
99import { Repository , IRef , IBranch , IRemote , IPushOptions } from './git' ;
10- import { throttle , anyEvent , eventToPromise } from './util' ;
10+ import { throttle , anyEvent , eventToPromise , filterEvent , mapEvent } from './util' ;
11+ import { watch } from './watch' ;
1112import { decorate , memoize , debounce } from 'core-decorators' ;
1213import * as path from 'path' ;
1314
@@ -242,7 +243,22 @@ export class Model {
242243 private repository : Repository ,
243244 onWorkspaceChange : Event < Uri >
244245 ) {
245- onWorkspaceChange ( this . onWorkspaceChange , this , this . disposables ) ;
246+ /* We use the native Node `watch` for faster, non debounced events.
247+ * That way we hopefully get the events during the operations we're
248+ * performing, thus sparing useless `git status` calls to refresh
249+ * the model's state.
250+ */
251+ const gitPath = path . join ( _repositoryRoot , '.git' ) ;
252+ const { event, disposable } = watch ( gitPath ) ;
253+ const onGitChange = mapEvent ( event , ( { filename } ) => Uri . file ( path . join ( gitPath , filename ) ) ) ;
254+ const onRelevantGitChange = filterEvent ( onGitChange , uri => ! / \/ \. g i t \/ i n d e x \. l o c k $ / . test ( uri . fsPath ) ) ;
255+ onRelevantGitChange ( this . onFSChange , this , this . disposables ) ;
256+ this . disposables . push ( disposable ) ;
257+
258+ const onNonGitChange = filterEvent ( onWorkspaceChange , uri => ! / \/ \. g i t \/ / . test ( uri . fsPath ) ) ;
259+ onNonGitChange ( this . onFSChange , this , this . disposables ) ;
260+
261+ this . status ( ) ;
246262 }
247263
248264 get repositoryRoot ( ) : string {
@@ -265,90 +281,18 @@ export class Model {
265281 }
266282
267283 @decorate ( throttle )
268- async update ( ) : Promise < void > {
269- await this . run ( Operation . Status , async ( ) => {
270- const status = await this . repository . getStatus ( ) ;
271- let HEAD : IBranch | undefined ;
272-
273- try {
274- HEAD = await this . repository . getHEAD ( ) ;
275-
276- if ( HEAD . name ) {
277- try {
278- HEAD = await this . repository . getBranch ( HEAD . name ) ;
279- } catch ( err ) {
280- // noop
281- }
282- }
283- } catch ( err ) {
284- // noop
285- }
286-
287- const [ refs , remotes ] = await Promise . all ( [ this . repository . getRefs ( ) , this . repository . getRemotes ( ) ] ) ;
288-
289- this . _HEAD = HEAD ;
290- this . _refs = refs ;
291- this . _remotes = remotes ;
292-
293- const index : Resource [ ] = [ ] ;
294- const workingTree : Resource [ ] = [ ] ;
295- const merge : Resource [ ] = [ ] ;
296-
297- status . forEach ( raw => {
298- const uri = Uri . file ( path . join ( this . repositoryRoot , raw . path ) ) ;
299-
300- switch ( raw . x + raw . y ) {
301- case '??' : return workingTree . push ( new Resource ( uri , Status . UNTRACKED ) ) ;
302- case '!!' : return workingTree . push ( new Resource ( uri , Status . IGNORED ) ) ;
303- case 'DD' : return merge . push ( new Resource ( uri , Status . BOTH_DELETED ) ) ;
304- case 'AU' : return merge . push ( new Resource ( uri , Status . ADDED_BY_US ) ) ;
305- case 'UD' : return merge . push ( new Resource ( uri , Status . DELETED_BY_THEM ) ) ;
306- case 'UA' : return merge . push ( new Resource ( uri , Status . ADDED_BY_THEM ) ) ;
307- case 'DU' : return merge . push ( new Resource ( uri , Status . DELETED_BY_US ) ) ;
308- case 'AA' : return merge . push ( new Resource ( uri , Status . BOTH_ADDED ) ) ;
309- case 'UU' : return merge . push ( new Resource ( uri , Status . BOTH_MODIFIED ) ) ;
310- }
311-
312- let isModifiedInIndex = false ;
313-
314- switch ( raw . x ) {
315- case 'M' : index . push ( new Resource ( uri , Status . INDEX_MODIFIED ) ) ; isModifiedInIndex = true ; break ;
316- case 'A' : index . push ( new Resource ( uri , Status . INDEX_ADDED ) ) ; break ;
317- case 'D' : index . push ( new Resource ( uri , Status . INDEX_DELETED ) ) ; break ;
318- case 'R' : index . push ( new Resource ( uri , Status . INDEX_RENAMED /*, raw.rename*/ ) ) ; break ;
319- case 'C' : index . push ( new Resource ( uri , Status . INDEX_COPIED ) ) ; break ;
320- }
321-
322- switch ( raw . y ) {
323- case 'M' : workingTree . push ( new Resource ( uri , Status . MODIFIED /*, raw.rename*/ ) ) ; break ;
324- case 'D' : workingTree . push ( new Resource ( uri , Status . DELETED /*, raw.rename*/ ) ) ; break ;
325- }
326- } ) ;
327-
328- this . _mergeGroup = new MergeGroup ( merge ) ;
329- this . _indexGroup = new IndexGroup ( index ) ;
330- this . _workingTreeGroup = new WorkingTreeGroup ( workingTree ) ;
331-
332- this . _onDidChange . fire ( this . resources ) ;
333- } ) ;
284+ async status ( ) : Promise < void > {
285+ await this . run ( Operation . Status ) ;
334286 }
335287
336288 @decorate ( throttle )
337289 async stage ( ...resources : Resource [ ] ) : Promise < void > {
338- await this . run ( Operation . Stage , async ( ) => {
339- const paths = resources . map ( r => r . uri . fsPath ) ;
340- await this . repository . add ( paths ) ;
341- await this . update ( ) ;
342- } ) ;
290+ await this . run ( Operation . Stage , ( ) => this . repository . add ( resources . map ( r => r . uri . fsPath ) ) ) ;
343291 }
344292
345293 @decorate ( throttle )
346294 async unstage ( ...resources : Resource [ ] ) : Promise < void > {
347- await this . run ( Operation . Unstage , async ( ) => {
348- const paths = resources . map ( r => r . uri . fsPath ) ;
349- await this . repository . revertFiles ( 'HEAD' , paths ) ;
350- await this . update ( ) ;
351- } ) ;
295+ await this . run ( Operation . Unstage , ( ) => this . repository . revertFiles ( 'HEAD' , resources . map ( r => r . uri . fsPath ) ) ) ;
352296 }
353297
354298 @decorate ( throttle )
@@ -359,7 +303,6 @@ export class Model {
359303 }
360304
361305 await this . repository . commit ( message , opts ) ;
362- await this . update ( ) ;
363306 } ) ;
364307 }
365308
@@ -393,72 +336,132 @@ export class Model {
393336 }
394337
395338 await Promise . all ( promises ) ;
396- await this . update ( ) ;
397339 } ) ;
398340 }
399341
400342 @decorate ( throttle )
401343 async branch ( name : string ) : Promise < void > {
402- await this . run ( Operation . Branch , async ( ) => {
403- await this . repository . branch ( name , true ) ;
404- await this . update ( ) ;
405- } ) ;
344+ await this . run ( Operation . Branch , ( ) => this . repository . branch ( name , true ) ) ;
406345 }
407346
408347 @decorate ( throttle )
409348 async checkout ( treeish : string ) : Promise < void > {
410- await this . run ( Operation . Checkout , async ( ) => {
411- await this . repository . checkout ( treeish , [ ] ) ;
412- await this . update ( ) ;
413- } ) ;
349+ await this . run ( Operation . Checkout , ( ) => this . repository . checkout ( treeish , [ ] ) ) ;
414350 }
415351
416352 @decorate ( throttle )
417353 async fetch ( ) : Promise < void > {
418- await this . run ( Operation . Fetch , async ( ) => {
419- await this . repository . fetch ( ) ;
420- await this . update ( ) ;
421- } ) ;
354+ await this . run ( Operation . Fetch , ( ) => this . repository . fetch ( ) ) ;
422355 }
423356
424357 @decorate ( throttle )
425358 async sync ( ) : Promise < void > {
426- await this . run ( Operation . Sync , async ( ) => {
427- await this . repository . sync ( ) ;
428- await this . update ( ) ;
429- } ) ;
359+ await this . run ( Operation . Sync , ( ) => this . repository . sync ( ) ) ;
430360 }
431361
432362 @decorate ( throttle )
433363 async push ( remote ?: string , name ?: string , options ?: IPushOptions ) : Promise < void > {
434- await this . run ( Operation . Push , async ( ) => {
435- await this . repository . push ( remote , name , options ) ;
436- await this . update ( ) ;
437- } ) ;
364+ await this . run ( Operation . Push , ( ) => this . repository . push ( remote , name , options ) ) ;
438365 }
439366
440- private async run ( operation : Operation , fn : ( ) => Promise < void > ) : Promise < void > {
367+ private async run ( operation : Operation , fn : ( ) => Promise < void > = ( ) => Promise . resolve ( ) ) : Promise < void > {
441368 this . _operations = this . _operations . start ( operation ) ;
442369 this . _onRunOperation . fire ( operation ) ;
443370
444371 try {
445372 await fn ( ) ;
373+ await this . update ( ) ;
446374 } finally {
447375 this . _operations = this . _operations . end ( operation ) ;
448376 this . _onDidRunOperation . fire ( operation ) ;
449377 }
450378 }
451379
380+ @decorate ( throttle )
381+ private async update ( ) : Promise < void > {
382+ const status = await this . repository . getStatus ( ) ;
383+ let HEAD : IBranch | undefined ;
384+
385+ try {
386+ HEAD = await this . repository . getHEAD ( ) ;
387+
388+ if ( HEAD . name ) {
389+ try {
390+ HEAD = await this . repository . getBranch ( HEAD . name ) ;
391+ } catch ( err ) {
392+ // noop
393+ }
394+ }
395+ } catch ( err ) {
396+ // noop
397+ }
398+
399+ const [ refs , remotes ] = await Promise . all ( [ this . repository . getRefs ( ) , this . repository . getRemotes ( ) ] ) ;
400+
401+ this . _HEAD = HEAD ;
402+ this . _refs = refs ;
403+ this . _remotes = remotes ;
404+
405+ const index : Resource [ ] = [ ] ;
406+ const workingTree : Resource [ ] = [ ] ;
407+ const merge : Resource [ ] = [ ] ;
408+
409+ status . forEach ( raw => {
410+ const uri = Uri . file ( path . join ( this . repositoryRoot , raw . path ) ) ;
411+
412+ switch ( raw . x + raw . y ) {
413+ case '??' : return workingTree . push ( new Resource ( uri , Status . UNTRACKED ) ) ;
414+ case '!!' : return workingTree . push ( new Resource ( uri , Status . IGNORED ) ) ;
415+ case 'DD' : return merge . push ( new Resource ( uri , Status . BOTH_DELETED ) ) ;
416+ case 'AU' : return merge . push ( new Resource ( uri , Status . ADDED_BY_US ) ) ;
417+ case 'UD' : return merge . push ( new Resource ( uri , Status . DELETED_BY_THEM ) ) ;
418+ case 'UA' : return merge . push ( new Resource ( uri , Status . ADDED_BY_THEM ) ) ;
419+ case 'DU' : return merge . push ( new Resource ( uri , Status . DELETED_BY_US ) ) ;
420+ case 'AA' : return merge . push ( new Resource ( uri , Status . BOTH_ADDED ) ) ;
421+ case 'UU' : return merge . push ( new Resource ( uri , Status . BOTH_MODIFIED ) ) ;
422+ }
423+
424+ let isModifiedInIndex = false ;
425+
426+ switch ( raw . x ) {
427+ case 'M' : index . push ( new Resource ( uri , Status . INDEX_MODIFIED ) ) ; isModifiedInIndex = true ; break ;
428+ case 'A' : index . push ( new Resource ( uri , Status . INDEX_ADDED ) ) ; break ;
429+ case 'D' : index . push ( new Resource ( uri , Status . INDEX_DELETED ) ) ; break ;
430+ case 'R' : index . push ( new Resource ( uri , Status . INDEX_RENAMED /*, raw.rename*/ ) ) ; break ;
431+ case 'C' : index . push ( new Resource ( uri , Status . INDEX_COPIED ) ) ; break ;
432+ }
433+
434+ switch ( raw . y ) {
435+ case 'M' : workingTree . push ( new Resource ( uri , Status . MODIFIED /*, raw.rename*/ ) ) ; break ;
436+ case 'D' : workingTree . push ( new Resource ( uri , Status . DELETED /*, raw.rename*/ ) ) ; break ;
437+ }
438+ } ) ;
439+
440+ this . _mergeGroup = new MergeGroup ( merge ) ;
441+ this . _indexGroup = new IndexGroup ( index ) ;
442+ this . _workingTreeGroup = new WorkingTreeGroup ( workingTree ) ;
443+
444+ this . _onDidChange . fire ( this . resources ) ;
445+ }
446+
447+ private onFSChange ( uri : Uri ) : void {
448+ if ( ! this . operations . isIdle ( ) ) {
449+ return ;
450+ }
451+
452+ this . eventuallyUpdateWhenIdleAndWait ( ) ;
453+ }
454+
452455 @debounce ( 1000 )
453- private onWorkspaceChange ( ) : void {
456+ private eventuallyUpdateWhenIdleAndWait ( ) : void {
454457 this . updateWhenIdleAndWait ( ) ;
455458 }
456459
457460 @decorate ( throttle )
458461 private async updateWhenIdleAndWait ( ) : Promise < void > {
459462 await this . whenIdle ( ) ;
460- await this . update ( ) ;
461- await new Promise ( c => setTimeout ( c , 7000 ) ) ;
463+ await this . status ( ) ;
464+ await new Promise ( c => setTimeout ( c , 5000 ) ) ;
462465 }
463466
464467 private async whenIdle ( ) : Promise < void > {
0 commit comments