@@ -169,6 +169,7 @@ function defaultExceptionHandler(ex: Error, cwd: string | undefined, start?: [nu
169169}
170170
171171const uniqueCounterForStdin = getScopedCounter ( ) ;
172+ const uniqueCounterForStream = getScopedCounter ( ) ;
172173
173174type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly : true } ;
174175export type PushForceOptions = { withLease : true ; ifIncludes ?: boolean } | { withLease : false ; ifIncludes ?: never } ;
@@ -392,14 +393,15 @@ export class Git implements Disposable {
392393 exception = undefined ;
393394 return { stdout : '' as T , stderr : result ?. stderr as T | undefined , exitCode : result ?. exitCode ?? 0 } ;
394395 } finally {
395- this . logGitCommand ( gitCommand , exception , getDurationMilliseconds ( start ) , waiting ) ;
396+ this . logGitCommandComplete ( gitCommand , exception , getDurationMilliseconds ( start ) , waiting ) ;
396397 }
397398 }
398399
399400 async * stream ( options : GitSpawnOptions , ...args : readonly ( string | undefined ) [ ] ) : AsyncGenerator < string > {
400401 if ( ! workspace . isTrusted ) throw new WorkspaceUntrustedError ( ) ;
401402
402403 const start = hrtime ( ) ;
404+ const streamId = uniqueCounterForStream . next ( ) ;
403405
404406 const { cancellation, configs, stdin, stdinEncoding, ...opts } = options ;
405407 const runArgs = args . filter ( a => a != null ) ;
@@ -457,6 +459,7 @@ export class Git implements Disposable {
457459
458460 const command = await this . path ( ) ;
459461 const proc = spawn ( command , runArgs , spawnOpts ) ;
462+
460463 if ( stdin ) {
461464 proc . stdin ?. end ( stdin , ( stdinEncoding ?? 'utf8' ) as BufferEncoding ) ;
462465 }
@@ -524,7 +527,23 @@ export class Git implements Disposable {
524527 } ) ;
525528 } ) ;
526529
530+ let cleanedUp = false ;
531+ const cleanup = ( ) => {
532+ if ( cleanedUp ) return ;
533+ cleanedUp = true ;
534+
535+ try {
536+ disposable ?. dispose ( ) ;
537+ } catch { }
538+ try {
539+ proc . removeAllListeners ( ) ;
540+ } catch { }
541+ this . logGitCommandComplete ( gitCommand , exception , getDurationMilliseconds ( start ) , false , streamId ) ;
542+ } ;
543+
527544 try {
545+ this . logGitCommandStart ( gitCommand , streamId ) ;
546+
528547 try {
529548 if ( proc . stdout ) {
530549 proc . stdout . setEncoding ( 'utf8' ) ;
@@ -544,11 +563,14 @@ export class Git implements Disposable {
544563 exception = ex ;
545564 throw ex ;
546565 } finally {
547- disposable ?. dispose ( ) ;
548- proc . removeAllListeners ( ) ;
549-
550- this . logGitCommand ( gitCommand , exception , getDurationMilliseconds ( start ) , false ) ;
566+ cleanup ( ) ;
551567 }
568+
569+ // Ensure cleanup happens immediately when the generator is explicitly closed (e.g., via break or return)
570+ // This is called by JavaScript when the generator is abandoned, ensuring logGitCommand is called
571+ // synchronously rather than waiting for garbage collection.
572+ // eslint-disable-next-line @typescript-eslint/no-meaningless-void-operator
573+ return void cleanup ( ) ;
552574 }
553575
554576 private _gitLocation : GitLocation | undefined ;
@@ -1685,14 +1707,25 @@ export class Git implements Disposable {
16851707 terminal . sendText ( text , options ?. execute ?? false ) ;
16861708 }
16871709
1688- private logGitCommand ( command : string , ex : Error | undefined , duration : number , waiting : boolean ) : void {
1710+ private logGitCommandStart ( command : string , id : number ) : void {
1711+ Logger . log ( `${ getLoggableScopeBlockOverride ( `GIT:→${ id } ` ) } ${ command } ${ GlyphChars . Dot } starting...` ) ;
1712+ this . logCore ( `${ getLoggableScopeBlockOverride ( `→${ id } ` , '' ) } ${ command } ${ GlyphChars . Dot } starting...` ) ;
1713+ }
1714+
1715+ private logGitCommandComplete (
1716+ command : string ,
1717+ ex : Error | undefined ,
1718+ duration : number ,
1719+ waiting : boolean ,
1720+ id ?: number ,
1721+ ) : void {
16891722 const slow = duration > slowCallWarningThreshold ;
16901723 const status = slow && waiting ? ' (slow, waiting)' : waiting ? ' (waiting)' : slow ? ' (slow)' : '' ;
16911724
16921725 if ( ex != null ) {
16931726 Logger . error (
16941727 undefined ,
1695- `${ getLoggableScopeBlockOverride ( 'GIT' ) } ${ command } ${ GlyphChars . Dot } ${
1728+ `${ getLoggableScopeBlockOverride ( id ? `GIT:← ${ id } ` : 'GIT' ) } ${ command } ${ GlyphChars . Dot } ${
16961729 isCancellationError ( ex )
16971730 ? 'cancelled'
16981731 : ( ex . message || String ( ex ) || '' )
@@ -1703,13 +1736,18 @@ export class Git implements Disposable {
17031736 ) ;
17041737 } else if ( slow ) {
17051738 Logger . warn (
1706- `${ getLoggableScopeBlockOverride ( 'GIT' , `*${ duration } ms` ) } ${ command } [*${ duration } ms]${ status } ` ,
1739+ `${ getLoggableScopeBlockOverride ( id ? `GIT:← ${ id } ` : 'GIT' , `*${ duration } ms` ) } ${ command } [*${ duration } ms]${ status } ` ,
17071740 ) ;
17081741 } else {
1709- Logger . log ( `${ getLoggableScopeBlockOverride ( 'GIT' , `${ duration } ms` ) } ${ command } [${ duration } ms]${ status } ` ) ;
1742+ Logger . log (
1743+ `${ getLoggableScopeBlockOverride ( id ? `GIT:←${ id } ` : 'GIT' , `${ duration } ms` ) } ${ command } [${ duration } ms]${ status } ` ,
1744+ ) ;
17101745 }
17111746
1712- this . logCore ( `${ getLoggableScopeBlockOverride ( slow ? '*' : '' , `${ duration } ms` ) } ${ command } ${ status } ` , ex ) ;
1747+ this . logCore (
1748+ `${ getLoggableScopeBlockOverride ( `${ id ? `←${ id } ` : '' } ${ slow ? '*' : '' } ` , `${ duration } ms` ) } ${ command } ${ status } ` ,
1749+ ex ,
1750+ ) ;
17131751 }
17141752
17151753 private _gitOutput : OutputChannel | undefined ;
0 commit comments