@@ -12,10 +12,10 @@ import { EventEmitter } from 'events';
1212import iconv = require( 'iconv-lite' ) ;
1313import * as filetype from 'file-type' ;
1414import { assign , groupBy , IDisposable , toDisposable , dispose , mkdirp , readBytes , detectUnicodeEncoding , Encoding , onceEvent , splitInChunks , Limiter } from './util' ;
15- import { CancellationToken , Progress } from 'vscode' ;
15+ import { CancellationToken , Progress , Uri } from 'vscode' ;
1616import { URI } from 'vscode-uri' ;
1717import { detectEncoding } from './encoding' ;
18- import { Ref , RefType , Branch , Remote , GitErrorCodes , LogOptions , Change , Status } from './api/git' ;
18+ import { Ref , RefType , Branch , Remote , GitErrorCodes , LogOptions , Change , Status , LogFileOptions } from './api/git' ;
1919import * as byline from 'byline' ;
2020import { StringDecoder } from 'string_decoder' ;
2121
@@ -318,7 +318,7 @@ function getGitErrorCode(stderr: string): string | undefined {
318318 return undefined ;
319319}
320320
321- const COMMIT_FORMAT = '%H\n%ae \n%P\n%B' ;
321+ const COMMIT_FORMAT = '%H\n%aN\n%aE\n%at \n%P\n%B' ;
322322
323323export class Git {
324324
@@ -503,7 +503,9 @@ export interface Commit {
503503 hash : string ;
504504 message : string ;
505505 parents : string [ ] ;
506- authorEmail ?: string | undefined ;
506+ authorDate ?: Date ;
507+ authorName ?: string ;
508+ authorEmail ?: string ;
507509}
508510
509511export class GitStatusParser {
@@ -634,14 +636,43 @@ export function parseGitmodules(raw: string): Submodule[] {
634636 return result ;
635637}
636638
637- export function parseGitCommit ( raw : string ) : Commit | null {
638- const match = / ^ ( [ 0 - 9 a - f ] { 40 } ) \n ( .* ) \n ( .* ) ( \n ( [ ^ ] * ) ) ? $ / m. exec ( raw . trim ( ) ) ;
639- if ( ! match ) {
640- return null ;
641- }
639+ const commitRegex = / ( [ 0 - 9 a - f ] { 40 } ) \n ( .* ) \n ( .* ) \n ( .* ) \n ( .* ) (?: \n ( [ ^ ] * ?) ) ? (?: \x00 ) / gm;
640+
641+ export function parseGitCommits ( data : string ) : Commit [ ] {
642+ let commits : Commit [ ] = [ ] ;
643+
644+ let ref ;
645+ let name ;
646+ let email ;
647+ let date ;
648+ let parents ;
649+ let message ;
650+ let match ;
651+
652+ do {
653+ match = commitRegex . exec ( data ) ;
654+ if ( match === null ) {
655+ break ;
656+ }
657+
658+ [ , ref , name , email , date , parents , message ] = match ;
642659
643- const parents = match [ 3 ] ? match [ 3 ] . split ( ' ' ) : [ ] ;
644- return { hash : match [ 1 ] , message : match [ 5 ] , parents, authorEmail : match [ 2 ] } ;
660+ if ( message [ message . length - 1 ] === '\n' ) {
661+ message = message . substr ( 0 , message . length - 1 ) ;
662+ }
663+
664+ // Stop excessive memory usage by using substr -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
665+ commits . push ( {
666+ hash : ` ${ ref } ` . substr ( 1 ) ,
667+ message : ` ${ message } ` . substr ( 1 ) ,
668+ parents : parents ? parents . split ( ' ' ) : [ ] ,
669+ authorDate : new Date ( Number ( date ) * 1000 ) ,
670+ authorName : ` ${ name } ` . substr ( 1 ) ,
671+ authorEmail : ` ${ email } ` . substr ( 1 )
672+ } ) ;
673+ } while ( true ) ;
674+
675+ return commits ;
645676}
646677
647678interface LsTreeElement {
@@ -760,38 +791,28 @@ export class Repository {
760791
761792 async log ( options ?: LogOptions ) : Promise < Commit [ ] > {
762793 const maxEntries = options && typeof options . maxEntries === 'number' && options . maxEntries > 0 ? options . maxEntries : 32 ;
763- const args = [ 'log' , '-' + maxEntries , `--pretty= format:${ COMMIT_FORMAT } %x00%x00` ] ;
794+ const args = [ 'log' , '-' + maxEntries , `--format:${ COMMIT_FORMAT } ` , '-z' ] ;
764795
765- const gitResult = await this . run ( args ) ;
766- if ( gitResult . exitCode ) {
796+ const result = await this . run ( args ) ;
797+ if ( result . exitCode ) {
767798 // An empty repo
768799 return [ ] ;
769800 }
770801
771- const s = gitResult . stdout ;
772- const result : Commit [ ] = [ ] ;
773- let index = 0 ;
774- while ( index < s . length ) {
775- let nextIndex = s . indexOf ( '\x00\x00' , index ) ;
776- if ( nextIndex === - 1 ) {
777- nextIndex = s . length ;
778- }
779-
780- let entry = s . substr ( index , nextIndex - index ) ;
781- if ( entry . startsWith ( '\n' ) ) {
782- entry = entry . substring ( 1 ) ;
783- }
802+ return parseGitCommits ( result . stdout ) ;
803+ }
784804
785- const commit = parseGitCommit ( entry ) ;
786- if ( ! commit ) {
787- break ;
788- }
805+ async logFile ( uri : Uri , options ?: LogFileOptions ) : Promise < Commit [ ] > {
806+ const maxEntries = options ?. maxEntries ?? 32 ;
807+ const args = [ 'log' , `-${ maxEntries } ` , `--format=${ COMMIT_FORMAT } ` , '-z' , '--' , uri . fsPath ] ;
789808
790- result . push ( commit ) ;
791- index = nextIndex + 2 ;
809+ const result = await this . run ( args ) ;
810+ if ( result . exitCode ) {
811+ // No file history, e.g. a new file or untracked
812+ return [ ] ;
792813 }
793814
794- return result ;
815+ return parseGitCommits ( result . stdout ) ;
795816 }
796817
797818 async bufferString ( object : string , encoding : string = 'utf8' , autoGuessEncoding = false ) : Promise < string > {
@@ -1853,8 +1874,12 @@ export class Repository {
18531874 }
18541875
18551876 async getCommit ( ref : string ) : Promise < Commit > {
1856- const result = await this . run ( [ 'show' , '-s' , `--format=${ COMMIT_FORMAT } ` , ref ] ) ;
1857- return parseGitCommit ( result . stdout ) || Promise . reject < Commit > ( 'bad commit format' ) ;
1877+ const result = await this . run ( [ 'show' , '-s' , `--format=${ COMMIT_FORMAT } ` , '-z' , ref ] ) ;
1878+ const commits = parseGitCommits ( result . stdout ) ;
1879+ if ( commits . length === 0 ) {
1880+ return Promise . reject < Commit > ( 'bad commit format' ) ;
1881+ }
1882+ return commits [ 0 ] ;
18581883 }
18591884
18601885 async updateSubmodules ( paths : string [ ] ) : Promise < void > {
0 commit comments