@@ -116,6 +116,24 @@ export function getShaAndDatesLogParser(includeTips?: boolean): ShaAndDatesLogPa
116116 return _shaAndDatesParser ;
117117}
118118
119+ type ShaAndDatesWithFilesLogParser = LogParserWithFiles < typeof shaAndDateMapping & { tips ?: string } > ;
120+ let _shaAndDatesWithFilesParser : ShaAndDatesWithFilesLogParser | undefined ;
121+
122+ type ShaAndDatesAndTipsWithFilesLogParser = LogParserWithFiles < typeof shaAndDateAndTipsMapping > ;
123+ let _shaAndDatesAndTipsWithFilesParser : ShaAndDatesAndTipsWithFilesLogParser | undefined ;
124+
125+ export function getShaAndDatesWithFilesLogParser (
126+ includeTips ?: boolean ,
127+ ) : ShaAndDatesWithFilesLogParser | ShaAndDatesAndTipsWithFilesLogParser {
128+ if ( includeTips ) {
129+ _shaAndDatesAndTipsWithFilesParser ??= createLogParserWithFiles ( shaAndDateAndTipsMapping ) ;
130+ return _shaAndDatesAndTipsWithFilesParser ;
131+ }
132+
133+ _shaAndDatesWithFilesParser ??= createLogParserWithFiles ( shaAndDateMapping ) ;
134+ return _shaAndDatesWithFilesParser ;
135+ }
136+
119137const shaMapping = { sha : '%H' } ;
120138
121139type ShaAndFilesAndStatsLogParser = LogParserWithFilesAndStats < typeof shaMapping > ;
@@ -208,7 +226,7 @@ type LogParsedEntryWithStats<T> = { [K in keyof T]: string } & { stats: LogParse
208226
209227// Parsed types
210228export interface LogParsedFile {
211- status : string ;
229+ status ? : string ;
212230 path : string ;
213231 originalPath ?: string ;
214232 additions ?: never ;
@@ -1080,3 +1098,128 @@ function createLogParserWithStats<T extends Record<string, string>>(
10801098 parseAsync : parseAsync ,
10811099 } ;
10821100}
1101+
1102+ function createLogParserWithFiles < T extends Record < string , string > > (
1103+ mapping : ExtractAll < T , string > ,
1104+ ) : LogParserWithFiles < T > {
1105+ let format = recordFormatSep ;
1106+ const keys : ( keyof ExtractAll < T , string > ) [ ] = [ ] ;
1107+ for ( const key in mapping ) {
1108+ keys . push ( key ) ;
1109+ format += `${ mapping [ key ] } ${ fieldFormatSep } ` ;
1110+ }
1111+
1112+ const args = [ `--format=${ format } ` , '--name-only' ] ;
1113+
1114+ function parseFileNames ( content : string ) : LogParsedFile [ ] {
1115+ const files : LogParsedFile [ ] = [ ] ;
1116+ if ( ! content ?. length ) return files ;
1117+
1118+ for ( const line of iterateByDelimiter ( content , '\n' ) ) {
1119+ const trimmed = line . trim ( ) ;
1120+ if ( ! trimmed ) continue ;
1121+
1122+ files . push ( { path : trimmed } ) ;
1123+ }
1124+
1125+ return files ;
1126+ }
1127+
1128+ function * parse ( data : string | Iterable < string > | undefined ) : Generator < LogParsedEntryWithFiles < T > > {
1129+ using sw = maybeStopWatch ( 'Git.createLogParserWithFiles.parse' , { log : false , logLevel : 'debug' } ) ;
1130+
1131+ if ( ! data ) {
1132+ sw ?. stop ( { suffix : ` no data` } ) ;
1133+ return ;
1134+ }
1135+
1136+ const records = iterateByDelimiter ( data , recordSep ) ;
1137+
1138+ let count = 0 ;
1139+ let entry : LogParsedEntryWithFiles < T > ;
1140+ let fields : IterableIterator < string > ;
1141+
1142+ for ( const record of records ) {
1143+ if ( ! record . length ) continue ;
1144+
1145+ count ++ ;
1146+ entry = { } as unknown as LogParsedEntryWithFiles < T > ;
1147+ fields = iterateByDelimiter ( record , fieldSep ) ;
1148+
1149+ let fieldCount = 0 ;
1150+ let field ;
1151+ while ( true ) {
1152+ field = fields . next ( ) ;
1153+ if ( field . done ) break ;
1154+
1155+ if ( fieldCount < keys . length ) {
1156+ entry [ keys [ fieldCount ++ ] ] = field . value as LogParsedEntryWithFiles < T > [ keyof T ] ;
1157+ } else if ( fieldCount === keys . length ) {
1158+ // Slice off the first newlines between the commit data and files, if any
1159+ const files = field . value . startsWith ( '\n\n' )
1160+ ? field . value . substring ( 2 )
1161+ : field . value . startsWith ( '\n' )
1162+ ? field . value . substring ( 1 )
1163+ : field . value ;
1164+ entry . files = parseFileNames ( files ) ;
1165+ } else {
1166+ debugger ;
1167+ }
1168+ }
1169+
1170+ yield entry ;
1171+ }
1172+
1173+ sw ?. stop ( { suffix : ` parsed ${ count } records` } ) ;
1174+ }
1175+
1176+ async function * parseAsync ( stream : AsyncGenerator < string > ) : AsyncGenerator < LogParsedEntryWithFiles < T > > {
1177+ using sw = maybeStopWatch ( 'Git.createLogParserWithFiles.parseAsync' , { log : false , logLevel : 'debug' } ) ;
1178+
1179+ const records = iterateAsyncByDelimiter ( stream , recordSep ) ;
1180+
1181+ let count = 0 ;
1182+ let entry : LogParsedEntryWithFiles < T > ;
1183+ let fields : IterableIterator < string > ;
1184+
1185+ for await ( const record of records ) {
1186+ if ( ! record . length ) continue ;
1187+
1188+ count ++ ;
1189+ entry = { } as unknown as LogParsedEntryWithFiles < T > ;
1190+ fields = iterateByDelimiter ( record , fieldSep ) ;
1191+
1192+ let fieldCount = 0 ;
1193+ let field ;
1194+ while ( true ) {
1195+ field = fields . next ( ) ;
1196+ if ( field . done ) break ;
1197+
1198+ if ( fieldCount < keys . length ) {
1199+ entry [ keys [ fieldCount ++ ] ] = field . value as LogParsedEntryWithFiles < T > [ keyof T ] ;
1200+ } else if ( fieldCount === keys . length ) {
1201+ // Slice off the first newlines between the commit data and files, if any
1202+ const files = field . value . startsWith ( '\n\n' )
1203+ ? field . value . substring ( 2 )
1204+ : field . value . startsWith ( '\n' )
1205+ ? field . value . substring ( 1 )
1206+ : field . value ;
1207+ entry . files = parseFileNames ( files ) ;
1208+ } else {
1209+ debugger ;
1210+ }
1211+ }
1212+
1213+ yield entry ;
1214+ }
1215+
1216+ sw ?. stop ( { suffix : ` parsed ${ count } records` } ) ;
1217+ }
1218+
1219+ return {
1220+ arguments : args ,
1221+ separators : { record : recordSep , field : fieldSep } ,
1222+ parse : parse ,
1223+ parseAsync : parseAsync ,
1224+ } ;
1225+ }
0 commit comments