@@ -12,9 +12,9 @@ import { EventEmitter } from 'events';
1212import iconv = require( 'iconv-lite' ) ;
1313import * as filetype from 'file-type' ;
1414import { assign , groupBy , denodeify , IDisposable , toDisposable , dispose , mkdirp , readBytes , detectUnicodeEncoding , Encoding , onceEvent } from './util' ;
15- import { CancellationToken } from 'vscode' ;
15+ import { CancellationToken , Uri } from 'vscode' ;
1616import { detectEncoding } from './encoding' ;
17- import { Ref , RefType , Branch , Remote , GitErrorCodes } from './api/git' ;
17+ import { Ref , RefType , Branch , Remote , GitErrorCodes , LogOptions , Change , Status } from './api/git' ;
1818
1919const readfile = denodeify < string , string | null , string > ( fs . readFile ) ;
2020
@@ -311,6 +311,8 @@ function getGitErrorCode(stderr: string): string | undefined {
311311 return undefined ;
312312}
313313
314+ const COMMIT_FORMAT = '%H\n%ae\n%P\n%B' ;
315+
314316export class Git {
315317
316318 readonly path : string ;
@@ -450,6 +452,7 @@ export interface Commit {
450452 hash : string ;
451453 message : string ;
452454 parents : string [ ] ;
455+ authorEmail ?: string | undefined ;
453456}
454457
455458export class GitStatusParser {
@@ -581,13 +584,13 @@ export function parseGitmodules(raw: string): Submodule[] {
581584}
582585
583586export function parseGitCommit ( raw : string ) : Commit | null {
584- const match = / ^ ( [ 0 - 9 a - f ] { 40 } ) \n ( .* ) \n ( [ ^ ] * ) $ / m. exec ( raw . trim ( ) ) ;
587+ const match = / ^ ( [ 0 - 9 a - f ] { 40 } ) \n ( .* ) \n ( . * ) \n ( [ ^ ] * ) $ / m. exec ( raw . trim ( ) ) ;
585588 if ( ! match ) {
586589 return null ;
587590 }
588591
589- const parents = match [ 2 ] ? match [ 2 ] . split ( ' ' ) : [ ] ;
590- return { hash : match [ 1 ] , message : match [ 3 ] , parents } ;
592+ const parents = match [ 3 ] ? match [ 3 ] . split ( ' ' ) : [ ] ;
593+ return { hash : match [ 1 ] , message : match [ 4 ] , parents, authorEmail : match [ 2 ] } ;
591594}
592595
593596interface LsTreeElement {
@@ -701,6 +704,41 @@ export class Repository {
701704 } ) ;
702705 }
703706
707+ async log ( options ?: LogOptions ) : Promise < Commit [ ] > {
708+ const maxEntries = options && typeof options . maxEntries === 'number' && options . maxEntries > 0 ? options . maxEntries : 32 ;
709+ const args = [ 'log' , '-' + maxEntries , `--pretty=format:${ COMMIT_FORMAT } %x00%x00` ] ;
710+ const gitResult = await this . run ( args ) ;
711+ if ( gitResult . exitCode ) {
712+ // An empty repo.
713+ return [ ] ;
714+ }
715+
716+ const s = gitResult . stdout ;
717+ const result : Commit [ ] = [ ] ;
718+ let index = 0 ;
719+ while ( index < s . length ) {
720+ let nextIndex = s . indexOf ( '\x00\x00' , index ) ;
721+ if ( nextIndex === - 1 ) {
722+ nextIndex = s . length ;
723+ }
724+
725+ let entry = s . substr ( index , nextIndex - index ) ;
726+ if ( entry . startsWith ( '\n' ) ) {
727+ entry = entry . substring ( 1 ) ;
728+ }
729+
730+ const commit = parseGitCommit ( entry ) ;
731+ if ( ! commit ) {
732+ break ;
733+ }
734+
735+ result . push ( commit ) ;
736+ index = nextIndex + 2 ;
737+ }
738+
739+ return result ;
740+ }
741+
704742 async bufferString ( object : string , encoding : string = 'utf8' , autoGuessEncoding = false ) : Promise < string > {
705743 const stdout = await this . buffer ( object ) ;
706744
@@ -855,25 +893,53 @@ export class Repository {
855893 return result . stdout ;
856894 }
857895
858- async diffWithHEAD ( path : string ) : Promise < string > {
896+ diffWithHEAD ( ) : Promise < Change [ ] > ;
897+ diffWithHEAD ( path : string ) : Promise < string > ;
898+ diffWithHEAD ( path ?: string | undefined ) : Promise < string | Change [ ] > ;
899+ async diffWithHEAD ( path ?: string | undefined ) : Promise < string | Change [ ] > {
900+ if ( ! path ) {
901+ return await this . diffFiles ( false ) ;
902+ }
903+
859904 const args = [ 'diff' , '--' , path ] ;
860905 const result = await this . run ( args ) ;
861906 return result . stdout ;
862907 }
863908
864- async diffWith ( ref : string , path : string ) : Promise < string > {
909+ diffWith ( ref : string ) : Promise < Change [ ] > ;
910+ diffWith ( ref : string , path : string ) : Promise < string > ;
911+ diffWith ( ref : string , path ?: string | undefined ) : Promise < string | Change [ ] > ;
912+ async diffWith ( ref : string , path ?: string ) : Promise < string | Change [ ] > {
913+ if ( ! path ) {
914+ return await this . diffFiles ( false , ref ) ;
915+ }
916+
865917 const args = [ 'diff' , ref , '--' , path ] ;
866918 const result = await this . run ( args ) ;
867919 return result . stdout ;
868920 }
869921
870- async diffIndexWithHEAD ( path : string ) : Promise < string > {
922+ diffIndexWithHEAD ( ) : Promise < Change [ ] > ;
923+ diffIndexWithHEAD ( path : string ) : Promise < string > ;
924+ diffIndexWithHEAD ( path ?: string | undefined ) : Promise < string | Change [ ] > ;
925+ async diffIndexWithHEAD ( path ?: string ) : Promise < string | Change [ ] > {
926+ if ( ! path ) {
927+ return await this . diffFiles ( true ) ;
928+ }
929+
871930 const args = [ 'diff' , '--cached' , '--' , path ] ;
872931 const result = await this . run ( args ) ;
873932 return result . stdout ;
874933 }
875934
876- async diffIndexWith ( ref : string , path : string ) : Promise < string > {
935+ diffIndexWith ( ref : string ) : Promise < Change [ ] > ;
936+ diffIndexWith ( ref : string , path : string ) : Promise < string > ;
937+ diffIndexWith ( ref : string , path ?: string | undefined ) : Promise < string | Change [ ] > ;
938+ async diffIndexWith ( ref : string , path ?: string ) : Promise < string | Change [ ] > {
939+ if ( ! path ) {
940+ return await this . diffFiles ( true , ref ) ;
941+ }
942+
877943 const args = [ 'diff' , '--cached' , ref , '--' , path ] ;
878944 const result = await this . run ( args ) ;
879945 return result . stdout ;
@@ -885,13 +951,102 @@ export class Repository {
885951 return result . stdout ;
886952 }
887953
888- async diffBetween ( ref1 : string , ref2 : string , path : string ) : Promise < string > {
889- const args = [ 'diff' , `${ ref1 } ...${ ref2 } ` , '--' , path ] ;
954+ diffBetween ( ref1 : string , ref2 : string ) : Promise < Change [ ] > ;
955+ diffBetween ( ref1 : string , ref2 : string , path : string ) : Promise < string > ;
956+ diffBetween ( ref1 : string , ref2 : string , path ?: string | undefined ) : Promise < string | Change [ ] > ;
957+ async diffBetween ( ref1 : string , ref2 : string , path ?: string ) : Promise < string | Change [ ] > {
958+ const range = `${ ref1 } ...${ ref2 } ` ;
959+ if ( ! path ) {
960+ return await this . diffFiles ( false , range ) ;
961+ }
962+
963+ const args = [ 'diff' , range , '--' , path ] ;
890964 const result = await this . run ( args ) ;
891965
892966 return result . stdout . trim ( ) ;
893967 }
894968
969+ private async diffFiles ( cached : boolean , ref ?: string ) : Promise < Change [ ] > {
970+ const args = [ 'diff' , '--name-status' , '-z' , '--diff-filter=ADMR' ] ;
971+ if ( cached ) {
972+ args . push ( '--cached' ) ;
973+ }
974+
975+ if ( ref ) {
976+ args . push ( ref ) ;
977+ }
978+
979+ const gitResult = await this . run ( args ) ;
980+ if ( gitResult . exitCode ) {
981+ return [ ] ;
982+ }
983+
984+ const entries = gitResult . stdout . split ( '\x00' ) ;
985+ let index = 0 ;
986+ const result : Change [ ] = [ ] ;
987+
988+ entriesLoop:
989+ while ( index < entries . length - 1 ) {
990+ const change = entries [ index ++ ] ;
991+ const resourcePath = entries [ index ++ ] ;
992+ if ( ! change || ! resourcePath ) {
993+ break ;
994+ }
995+
996+ const originalUri = Uri . file ( path . isAbsolute ( resourcePath ) ? resourcePath : path . join ( this . repositoryRoot , resourcePath ) ) ;
997+ let status : Status = Status . UNTRACKED ;
998+
999+ // Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status.
1000+ switch ( change [ 0 ] ) {
1001+ case 'M' :
1002+ status = Status . MODIFIED ;
1003+ break ;
1004+
1005+ case 'A' :
1006+ status = Status . INDEX_ADDED ;
1007+ break ;
1008+
1009+ case 'D' :
1010+ status = Status . DELETED ;
1011+ break ;
1012+
1013+ // Rename contains two paths, the second one is what the file is renamed/copied to.
1014+ case 'R' :
1015+ if ( index >= entries . length ) {
1016+ break ;
1017+ }
1018+
1019+ const newPath = entries [ index ++ ] ;
1020+ if ( ! newPath ) {
1021+ break ;
1022+ }
1023+
1024+ const uri = Uri . file ( path . isAbsolute ( newPath ) ? newPath : path . join ( this . repositoryRoot , newPath ) ) ;
1025+ result . push ( {
1026+ uri,
1027+ renameUri : uri ,
1028+ originalUri,
1029+ status : Status . INDEX_RENAMED
1030+ } ) ;
1031+
1032+ continue ;
1033+
1034+ default :
1035+ // Unknown status
1036+ break entriesLoop;
1037+ }
1038+
1039+ result . push ( {
1040+ status,
1041+ originalUri,
1042+ uri : originalUri ,
1043+ renameUri : originalUri ,
1044+ } ) ;
1045+ }
1046+
1047+ return result ;
1048+ }
1049+
8951050 async getMergeBase ( ref1 : string , ref2 : string ) : Promise < string > {
8961051 const args = [ 'merge-base' , ref1 , ref2 ] ;
8971052 const result = await this . run ( args ) ;
@@ -1557,7 +1712,7 @@ export class Repository {
15571712 }
15581713
15591714 async getCommit ( ref : string ) : Promise < Commit > {
1560- const result = await this . run ( [ 'show' , '-s' , ' --format=%H\n%P\n%B' , ref ] ) ;
1715+ const result = await this . run ( [ 'show' , '-s' , ` --format=${ COMMIT_FORMAT } ` , ref ] ) ;
15611716 return parseGitCommit ( result . stdout ) || Promise . reject < Commit > ( 'bad commit format' ) ;
15621717 }
15631718
0 commit comments