11'use strict' ;
2- import { Functions , Iterables , Objects , TernarySearchTree } from './system' ;
2+ import { Functions , Iterables , Objects , Strings , TernarySearchTree } from './system' ;
33import { ConfigurationChangeEvent , Disposable , Event , EventEmitter , Range , TextDocument , TextDocumentChangeEvent , TextEditor , Uri , window , WindowState , workspace , WorkspaceFolder , WorkspaceFoldersChangeEvent } from 'vscode' ;
44import { configuration , IConfig , IRemotesConfig } from './configuration' ;
55import { CommandContext , DocumentSchemes , setCommandContext } from './constants' ;
@@ -536,7 +536,7 @@ export class GitService extends Disposable {
536536 if ( entry && entry . key ) {
537537 this . _onDidBlameFail . fire ( entry . key ) ;
538538 }
539- return await GitService . emptyPromise as GitBlame ;
539+ return GitService . emptyPromise as Promise < GitBlame > ;
540540 }
541541
542542 const [ file , root ] = Git . splitPath ( uri . fsPath , uri . repoPath , false ) ;
@@ -558,7 +558,82 @@ export class GitService extends Disposable {
558558 } as CachedBlame ) ;
559559
560560 this . _onDidBlameFail . fire ( entry . key ) ;
561- return await GitService . emptyPromise as GitBlame ;
561+ return GitService . emptyPromise as Promise < GitBlame > ;
562+ }
563+
564+ return undefined ;
565+ }
566+ }
567+
568+ async getBlameForFileContents ( uri : GitUri , contents : string ) : Promise < GitBlame | undefined > {
569+ const key = `blame:${ Strings . sha1 ( contents ) } ` ;
570+
571+ let entry : GitCacheEntry | undefined ;
572+ if ( this . UseCaching ) {
573+ const cacheKey = this . getCacheEntryKey ( uri ) ;
574+ entry = this . _gitCache . get ( cacheKey ) ;
575+
576+ if ( entry !== undefined ) {
577+ const cachedBlame = entry . get < CachedBlame > ( key ) ;
578+ if ( cachedBlame !== undefined ) {
579+ Logger . log ( `getBlameForFileContents[Cached(${ key } )]('${ uri . repoPath } ', '${ uri . fsPath } ', '${ uri . sha } ')` ) ;
580+ return cachedBlame . item ;
581+ }
582+ }
583+
584+ Logger . log ( `getBlameForFileContents[Not Cached(${ key } )]('${ uri . repoPath } ', '${ uri . fsPath } ', '${ uri . sha } ')` ) ;
585+
586+ if ( entry === undefined ) {
587+ entry = new GitCacheEntry ( cacheKey ) ;
588+ this . _gitCache . set ( entry . key , entry ) ;
589+ }
590+ }
591+ else {
592+ Logger . log ( `getBlameForFileContents('${ uri . repoPath } ', '${ uri . fsPath } ', '${ uri . sha } ')` ) ;
593+ }
594+
595+ const promise = this . getBlameForFileContentsCore ( uri , contents , entry , key ) ;
596+
597+ if ( entry ) {
598+ Logger . log ( `Add blame cache for '${ entry . key } :${ key } '` ) ;
599+
600+ entry . set < CachedBlame > ( key , {
601+ item : promise
602+ } as CachedBlame ) ;
603+ }
604+
605+ return promise ;
606+ }
607+
608+ async getBlameForFileContentsCore ( uri : GitUri , contents : string , entry : GitCacheEntry | undefined , key : string ) : Promise < GitBlame | undefined > {
609+ if ( ! ( await this . isTracked ( uri ) ) ) {
610+ Logger . log ( `Skipping blame; '${ uri . fsPath } ' is not tracked` ) ;
611+ if ( entry && entry . key ) {
612+ this . _onDidBlameFail . fire ( entry . key ) ;
613+ }
614+ return GitService . emptyPromise as Promise < GitBlame > ;
615+ }
616+
617+ const [ file , root ] = Git . splitPath ( uri . fsPath , uri . repoPath , false ) ;
618+
619+ try {
620+ const data = await Git . blame_contents ( root , file , contents , { ignoreWhitespace : this . config . blame . ignoreWhitespace } ) ;
621+ const blame = GitBlameParser . parse ( data , root , file ) ;
622+ return blame ;
623+ }
624+ catch ( ex ) {
625+ // Trap and cache expected blame errors
626+ if ( entry ) {
627+ const msg = ex && ex . toString ( ) ;
628+ Logger . log ( `Replace blame cache with empty promise for '${ entry . key } :${ key } '` ) ;
629+
630+ entry . set < CachedBlame > ( key , {
631+ item : GitService . emptyPromise ,
632+ errorMessage : msg
633+ } as CachedBlame ) ;
634+
635+ this . _onDidBlameFail . fire ( entry . key ) ;
636+ return GitService . emptyPromise as Promise < GitBlame > ;
562637 }
563638
564639 return undefined ;
@@ -582,16 +657,17 @@ export class GitService extends Disposable {
582657 if ( commit === undefined ) return undefined ;
583658
584659 return {
585- author : Object . assign ( { } , blame . authors . get ( commit . author ) , { lineCount : commit . lines . length } ) ,
660+ author : { ... blame . authors . get ( commit . author ) , lineCount : commit . lines . length } ,
586661 commit : commit ,
587662 line : blameLine
588663 } as GitBlameLine ;
589664 }
590665
666+ const lineToBlame = line + 1 ;
591667 const fileName = uri . fsPath ;
592668
593669 try {
594- const data = await Git . blame ( uri . repoPath , fileName , uri . sha , { ignoreWhitespace : this . config . blame . ignoreWhitespace , startLine : line + 1 , endLine : line + 1 } ) ;
670+ const data = await Git . blame ( uri . repoPath , fileName , uri . sha , { ignoreWhitespace : this . config . blame . ignoreWhitespace , startLine : lineToBlame , endLine : lineToBlame } ) ;
595671 const blame = GitBlameParser . parse ( data , uri . repoPath , fileName ) ;
596672 if ( blame === undefined ) return undefined ;
597673
@@ -601,7 +677,49 @@ export class GitService extends Disposable {
601677 line : blame . lines [ line ]
602678 } as GitBlameLine ;
603679 }
604- catch ( ex ) {
680+ catch {
681+ return undefined ;
682+ }
683+ }
684+
685+ async getBlameForLineContents ( uri : GitUri , line : number , contents : string ) : Promise < GitBlameLine | undefined > {
686+ Logger . log ( `getBlameForLineContents('${ uri . repoPath } ', '${ uri . fsPath } ', ${ line } )` ) ;
687+
688+ if ( this . UseCaching ) {
689+ const blame = await this . getBlameForFileContents ( uri , contents ) ;
690+ if ( blame === undefined ) return undefined ;
691+
692+ let blameLine = blame . lines [ line ] ;
693+ if ( blameLine === undefined ) {
694+ if ( blame . lines . length !== line ) return undefined ;
695+ blameLine = blame . lines [ line - 1 ] ;
696+ }
697+
698+ const commit = blame . commits . get ( blameLine . sha ) ;
699+ if ( commit === undefined ) return undefined ;
700+
701+ return {
702+ author : { ...blame . authors . get ( commit . author ) , lineCount : commit . lines . length } ,
703+ commit : commit ,
704+ line : blameLine
705+ } as GitBlameLine ;
706+ }
707+
708+ const lineToBlame = line + 1 ;
709+ const fileName = uri . fsPath ;
710+
711+ try {
712+ const data = await Git . blame_contents ( uri . repoPath , fileName , contents , { ignoreWhitespace : this . config . blame . ignoreWhitespace , startLine : lineToBlame , endLine : lineToBlame } ) ;
713+ const blame = GitBlameParser . parse ( data , uri . repoPath , fileName ) ;
714+ if ( blame === undefined ) return undefined ;
715+
716+ return {
717+ author : Iterables . first ( blame . authors . values ( ) ) ,
718+ commit : Iterables . first ( blame . commits . values ( ) ) ,
719+ line : blame . lines [ line ]
720+ } as GitBlameLine ;
721+ }
722+ catch {
605723 return undefined ;
606724 }
607725 }
@@ -618,10 +736,10 @@ export class GitService extends Disposable {
618736 getBlameForRangeSync ( blame : GitBlame , uri : GitUri , range : Range ) : GitBlameLines | undefined {
619737 Logger . log ( `getBlameForRangeSync('${ uri . repoPath } ', '${ uri . fsPath } ', '${ uri . sha } ', [${ range . start . line } , ${ range . end . line } ])` ) ;
620738
621- if ( blame . lines . length === 0 ) return Object . assign ( { allLines : blame . lines } , blame ) ;
739+ if ( blame . lines . length === 0 ) return { allLines : blame . lines , ... blame } ;
622740
623741 if ( range . start . line === 0 && range . end . line === blame . lines . length - 1 ) {
624- return Object . assign ( { allLines : blame . lines } , blame ) ;
742+ return { allLines : blame . lines , ... blame } ;
625743 }
626744
627745 const lines = blame . lines . slice ( range . start . line , range . end . line + 1 ) ;
@@ -778,7 +896,7 @@ export class GitService extends Disposable {
778896 errorMessage : msg
779897 } as CachedDiff ) ;
780898
781- return await GitService . emptyPromise as GitDiff ;
899+ return GitService . emptyPromise as Promise < GitDiff > ;
782900 }
783901
784902 return undefined ;
@@ -982,7 +1100,7 @@ export class GitService extends Disposable {
9821100 private async getLogForFileCore ( repoPath : string | undefined , fileName : string , options : { maxCount ?: number , range ?: Range , ref ?: string , reverse ?: boolean , skipMerges ?: boolean } , entry : GitCacheEntry | undefined , key : string ) : Promise < GitLog | undefined > {
9831101 if ( ! ( await this . isTracked ( fileName , repoPath , options . ref ) ) ) {
9841102 Logger . log ( `Skipping log; '${ fileName } ' is not tracked` ) ;
985- return await GitService . emptyPromise as GitLog ;
1103+ return GitService . emptyPromise as Promise < GitLog > ;
9861104 }
9871105
9881106 const [ file , root ] = Git . splitPath ( fileName , repoPath , false ) ;
@@ -1015,7 +1133,7 @@ export class GitService extends Disposable {
10151133 errorMessage : msg
10161134 } as CachedLog ) ;
10171135
1018- return await GitService . emptyPromise as GitLog ;
1136+ return GitService . emptyPromise as Promise < GitLog > ;
10191137 }
10201138
10211139 return undefined ;
0 commit comments