@@ -6,11 +6,11 @@ import { Git, GitAuthor, GitCommitType, GitLog, GitLogCommit, GitStatusFileStatu
66import * as path from 'path' ;
77
88interface LogEntry {
9- sha : string ;
9+ ref ? : string ;
1010
11- author : string ;
12- authorDate ?: string ;
13- authorEmail ?: string ;
11+ author ? : string ;
12+ date ?: string ;
13+ email ?: string ;
1414
1515 parentShas ?: string [ ] ;
1616
@@ -24,142 +24,105 @@ interface LogEntry {
2424}
2525
2626const diffRegex = / d i f f - - g i t a \/ ( .* ) b \/ ( .* ) / ;
27+ const emptyEntry : LogEntry = { } ;
2728
2829export class GitLogParser {
2930
3031 static parse ( data : string , type : GitCommitType , repoPath : string | undefined , fileName : string | undefined , sha : string | undefined , maxCount : number | undefined , reverse : boolean , range : Range | undefined ) : GitLog | undefined {
3132 if ( ! data ) return undefined ;
3233
33- const authors : Map < string , GitAuthor > = new Map ( ) ;
34- const commits : Map < string , GitLogCommit > = new Map ( ) ;
35-
3634 let relativeFileName : string ;
3735 let recentCommit : GitLogCommit | undefined = undefined ;
3836
39- if ( repoPath !== undefined ) {
40- repoPath = Strings . normalizePath ( repoPath ) ;
41- }
42-
43- let entry : LogEntry | undefined = undefined ;
37+ let entry : LogEntry = emptyEntry ;
4438 let line : string | undefined = undefined ;
45- let lineParts : string [ ] ;
46- let next : IteratorResult < string > | undefined = undefined ;
39+ let token : number ;
4740
4841 let i = 0 ;
4942 let first = true ;
50- let skip = false ;
5143
52- const lines = Strings . lines ( data ) ;
44+ const lines = Strings . lines ( data + '\n</f>' ) ;
45+ // Skip the first line since it will always be </f>
46+ let next = lines . next ( ) ;
47+ if ( next . done ) return undefined ;
48+
49+ if ( repoPath !== undefined ) {
50+ repoPath = Strings . normalizePath ( repoPath ) ;
51+ }
52+
53+ const authors : Map < string , GitAuthor > = new Map ( ) ;
54+ const commits : Map < string , GitLogCommit > = new Map ( ) ;
55+
5356 while ( true ) {
54- if ( ! skip ) {
55- next = lines . next ( ) ;
56- if ( next . done ) break ;
57+ next = lines . next ( ) ;
58+ if ( next . done ) break ;
5759
58- line = next . value ;
59- }
60- else {
61- skip = false ;
62- }
60+ line = next . value ;
6361
6462 // Since log --reverse doesn't properly honor a max count -- enforce it here
6563 if ( reverse && maxCount && ( i >= maxCount ) ) break ;
6664
67- lineParts = line ! . split ( ' ' ) ;
68- if ( lineParts . length < 2 ) continue ;
65+ // <<1-char token>> <data>
66+ // e.g. <r> bd1452a2dc
67+ token = line . charCodeAt ( 1 ) ;
6968
70- if ( entry === undefined ) {
71- if ( ! Git . shaRegex . test ( lineParts [ 0 ] ) ) continue ;
72-
73- entry = {
74- sha : lineParts [ 0 ]
75- } as LogEntry ;
76-
77- continue ;
78- }
69+ switch ( token ) {
70+ case 114 : // 'r': // ref
71+ entry = {
72+ ref : line . substring ( 4 )
73+ } ;
74+ break ;
7975
80- switch ( lineParts [ 0 ] ) {
81- case 'author' :
82- entry . author = Git . isUncommitted ( entry . sha )
76+ case 97 : // 'a': // author
77+ entry . author = Git . isUncommitted ( entry . ref )
8378 ? 'You'
84- : lineParts . slice ( 1 ) . join ( ' ' ) . trim ( ) ;
79+ : line . substring ( 4 ) ;
8580 break ;
8681
87- case ' author-mail' :
88- entry . authorEmail = lineParts . slice ( 1 ) . join ( ' ' ) . trim ( ) ;
82+ case 101 : // 'e': // author-mail
83+ entry . email = line . substring ( 4 ) ;
8984 break ;
9085
91- case ' author-date' :
92- entry . authorDate = lineParts [ 1 ] ;
86+ case 100 : // 'd': // author-date
87+ entry . date = line . substring ( 4 ) ;
9388 break ;
9489
95- case 'parents' :
96- entry . parentShas = lineParts . slice ( 1 ) ;
90+ case 112 : // 'p': // parents
91+ entry . parentShas = line . substring ( 4 ) . split ( ' ' ) ;
9792 break ;
9893
99- case 'summary' :
100- entry . summary = lineParts . slice ( 1 ) . join ( ' ' ) . trim ( ) ;
94+ case 115 : // 's': // summary
10195 while ( true ) {
10296 next = lines . next ( ) ;
10397 if ( next . done ) break ;
10498
10599 line = next . value ;
106- if ( ! line ) break ;
100+ if ( line === '</s>' ) break ;
107101
108- if ( line === 'filename ?' ) {
109- skip = true ;
110- break ;
102+ if ( entry . summary === undefined ) {
103+ entry . summary = line ;
104+ }
105+ else {
106+ entry . summary += `\n${ line } ` ;
111107 }
112-
113- entry . summary += `\n${ line } ` ;
114108 }
115109 break ;
116110
117- case 'filename' :
118- if ( type === GitCommitType . Branch ) {
111+ case 102 : // 'f': // files
112+ // Skip the blank line git adds before the files
113+ next = lines . next ( ) ;
114+ if ( next . done || next . value === '</f>' ) break ;
115+
116+ while ( true ) {
119117 next = lines . next ( ) ;
120118 if ( next . done ) break ;
121119
122120 line = next . value ;
121+ if ( line === '</f>' ) break ;
123122
124- // If the next line isn't blank, make sure it isn't starting a new commit or s git warning
125- if ( line && ( Git . shaRegex . test ( line ) || line . startsWith ( 'warning:' ) ) ) {
126- skip = true ;
127- continue ;
128- }
129-
130- let diff = false ;
131- while ( true ) {
132- next = lines . next ( ) ;
133- if ( next . done ) break ;
134-
135- line = next . value ;
136- lineParts = line . split ( ' ' ) ;
137-
138- // make sure the line isn't starting a new commit or s git warning
139- if ( Git . shaRegex . test ( lineParts [ 0 ] ) || line . startsWith ( 'warning:' ) ) {
140- skip = true ;
141- break ;
142- }
143-
144- if ( diff ) continue ;
145-
146- if ( lineParts [ 0 ] === 'diff' ) {
147- diff = true ;
148- const matches = diffRegex . exec ( line ) ;
149- if ( matches != null ) {
150- entry . fileName = matches [ 1 ] ;
151- const originalFileName = matches [ 2 ] ;
152- if ( entry . fileName !== originalFileName ) {
153- entry . originalFileName = originalFileName ;
154- }
155- }
156- continue ;
157- }
158-
159- if ( entry . fileStatuses == null ) {
160- entry . fileStatuses = [ ] ;
161- }
123+ if ( line . startsWith ( 'warning:' ) ) continue ;
162124
125+ if ( type === GitCommitType . Branch ) {
163126 const status = {
164127 status : line [ 0 ] as GitStatusFileStatus ,
165128 fileName : line . substring ( 1 ) ,
@@ -168,28 +131,41 @@ export class GitLogParser {
168131 this . parseFileName ( status ) ;
169132
170133 if ( status . fileName ) {
134+ if ( entry . fileStatuses === undefined ) {
135+ entry . fileStatuses = [ ] ;
136+ }
171137 entry . fileStatuses . push ( status ) ;
172138 }
173139 }
140+ else if ( line . startsWith ( 'diff' ) ) {
141+ const matches = diffRegex . exec ( line ) ;
142+ if ( matches != null ) {
143+ entry . fileName = matches [ 1 ] ;
144+ const originalFileName = matches [ 2 ] ;
145+ if ( entry . fileName !== originalFileName ) {
146+ entry . originalFileName = originalFileName ;
147+ }
148+ entry . status = entry . fileName !== entry . originalFileName ? 'R' : 'M' ;
149+ }
174150
175- if ( entry . fileStatuses ) {
176- entry . fileName = Arrays . filterMap ( entry . fileStatuses ,
177- f => ! ! f . fileName ? f . fileName : undefined ) . join ( ', ' ) ;
151+ while ( true ) {
152+ next = lines . next ( ) ;
153+ if ( next . done || next . value === '</f>' ) break ;
154+ }
155+ break ;
178156 }
179- }
180- else {
181- lines . next ( ) ;
182- next = lines . next ( ) ;
183-
184- line = next . value ;
185-
186- if ( line !== undefined && ! line . startsWith ( 'warning:' ) ) {
157+ else {
187158 entry . status = line [ 0 ] as GitStatusFileStatus ;
188159 entry . fileName = line . substring ( 1 ) ;
189160 this . parseFileName ( entry ) ;
190161 }
191162 }
192163
164+ if ( entry . fileStatuses !== undefined ) {
165+ entry . fileName = Arrays . filterMap ( entry . fileStatuses ,
166+ f => ! ! f . fileName ? f . fileName : undefined ) . join ( ', ' ) ;
167+ }
168+
193169 if ( first && repoPath === undefined && type === GitCommitType . File && fileName !== undefined ) {
194170 // Try to get the repoPath from the most recent commit
195171 repoPath = Strings . normalizePath ( fileName . replace ( fileName . startsWith ( '/' ) ? `/${ entry . fileName } ` : entry . fileName ! , '' ) ) ;
@@ -200,18 +176,14 @@ export class GitLogParser {
200176 }
201177 first = false ;
202178
203- const commit = commits . get ( entry . sha ) ;
179+ const commit = commits . get ( entry . ref ! ) ;
204180 if ( commit === undefined ) {
205181 i ++ ;
206182 }
207183 recentCommit = GitLogParser . parseEntry ( entry , commit , type , repoPath , relativeFileName , commits , authors , recentCommit ) ;
208184
209- entry = undefined ;
210185 break ;
211186 }
212-
213- if ( next ! . done ) break ;
214-
215187 }
216188
217189 return {
@@ -247,10 +219,10 @@ export class GitLogParser {
247219 commit = new GitLogCommit (
248220 type ,
249221 repoPath ! ,
250- entry . sha ,
251- entry . author ,
252- entry . authorEmail ,
253- new Date ( entry . authorDate ! as any * 1000 ) ,
222+ entry . ref ! ,
223+ entry . author ! ,
224+ entry . email ,
225+ new Date ( entry . date ! as any * 1000 ) ,
254226 entry . summary ! ,
255227 relativeFileName ,
256228 entry . fileStatuses || [ ] ,
@@ -261,7 +233,7 @@ export class GitLogParser {
261233 entry . parentShas !
262234 ) ;
263235
264- commits . set ( entry . sha , commit ) ;
236+ commits . set ( entry . ref ! , commit ) ;
265237 }
266238 // else {
267239 // Logger.log(`merge commit? ${entry.sha}`);
@@ -282,7 +254,7 @@ export class GitLogParser {
282254 return commit ;
283255 }
284256
285- private static parseFileName ( entry : { fileName ?: string , originalFileName ?: string } ) {
257+ static parseFileName ( entry : { fileName ?: string , originalFileName ?: string } ) {
286258 if ( entry . fileName === undefined ) return ;
287259
288260 const index = entry . fileName . indexOf ( '\t' ) + 1 ;
0 commit comments