1 package org.apache.maven.scm.provider.git.gitexe.command.changelog;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.scm.ChangeFile;
23 import org.apache.maven.scm.ChangeSet;
24 import org.apache.maven.scm.ScmFileStatus;
25 import org.apache.maven.scm.util.AbstractConsumer;
26
27 import java.util.ArrayList;
28 import java.util.Calendar;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.TimeZone;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36
37
38
39
40
41 public class GitChangeLogConsumer
42 extends AbstractConsumer
43 {
44
45
46
47
48 private static final String GIT_TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss Z";
49
50
51
52
53 private static final int STATUS_GET_HEADER = 1;
54
55
56
57
58 private static final int STATUS_GET_AUTHOR = 2;
59
60
61
62
63 private static final int STATUS_RAW_TREE = 21;
64
65
66
67
68 private static final int STATUS_RAW_PARENT = 22;
69
70
71
72
73 private static final int STATUS_RAW_AUTHOR = 23;
74
75
76
77
78 private static final int STATUS_RAW_COMMITTER = 24;
79
80
81
82
83 private static final int STATUS_GET_DATE = 3;
84
85
86
87
88 private static final int STATUS_GET_FILE = 4;
89
90
91
92
93 private static final int STATUS_GET_COMMENT = 5;
94
95
96
97
98 private static final Pattern HEADER_PATTERN = Pattern.compile( "^commit ([A-Fa-f0-9]+)(?: \\((.*)\\))?$" );
99
100
101
102
103 private static final Pattern AUTHOR_PATTERN = Pattern.compile( "^Author: (.*)" );
104
105
106
107
108 private static final Pattern RAW_TREE_PATTERN = Pattern.compile( "^tree ([A-Fa-f0-9]+)" );
109
110
111
112
113 private static final Pattern RAW_PARENT_PATTERN = Pattern.compile( "^parent ([A-Fa-f0-9]+)" );
114
115
116
117
118 private static final Pattern RAW_AUTHOR_PATTERN = Pattern.compile( "^author (.+ <.+>) ([0-9]+) (.*)" );
119
120
121
122
123 private static final Pattern RAW_COMMITTER_PATTERN = Pattern.compile( "^committer (.+ <.+>) ([0-9]+) (.*)" );
124
125
126
127
128 private static final Pattern DATE_PATTERN = Pattern.compile( "^Date:\\s*(.*)" );
129
130
131
132
133 private static final Pattern FILE_PATTERN =
134 Pattern.compile( "^:\\d* \\d* [A-Fa-f0-9]*\\.* [A-Fa-f0-9]*\\.* ([A-Z])[0-9]*\\t([^\\t]*)(\\t(.*))?" );
135
136
137
138
139 private int status = STATUS_GET_HEADER;
140
141
142
143
144 private final List<ChangeSet> entries = new ArrayList<>();
145
146
147
148
149 private ChangeSet currentChange;
150
151
152
153
154 private String currentRevision;
155
156
157
158
159 private StringBuilder currentComment;
160
161 private final String userDateFormat;
162
163
164
165
166 public GitChangeLogConsumer( String userDateFormat )
167 {
168 this.userDateFormat = userDateFormat;
169 }
170
171 public List<ChangeSet> getModifications()
172 {
173
174 processGetFile( "" );
175
176 return entries;
177 }
178
179
180
181
182
183
184
185
186 public void consumeLine( String line )
187 {
188 switch ( status )
189 {
190 case STATUS_GET_HEADER:
191 processGetHeader( line );
192 break;
193 case STATUS_GET_AUTHOR:
194 processGetAuthor( line );
195 break;
196 case STATUS_GET_DATE:
197 processGetDate( line, null );
198 break;
199 case STATUS_GET_COMMENT:
200 processGetComment( line );
201 break;
202 case STATUS_GET_FILE:
203 processGetFile( line );
204 break;
205 case STATUS_RAW_TREE:
206 processGetRawTree( line );
207 break;
208 case STATUS_RAW_PARENT:
209 processGetRawParent( line );
210 break;
211 case STATUS_RAW_AUTHOR:
212 processGetRawAuthor( line );
213 break;
214 case STATUS_RAW_COMMITTER:
215 processGetRawCommitter( line );
216 break;
217 default:
218 throw new IllegalStateException( "Unknown state: " + status );
219 }
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 private void processGetHeader( String line )
236 {
237 Matcher matcher = HEADER_PATTERN.matcher( line );
238 if ( !matcher.matches() )
239 {
240 return;
241 }
242
243 currentRevision = matcher.group( 1 );
244
245 currentChange = new ChangeSet();
246
247 currentChange.setRevision( currentRevision );
248
249
250 String tagList = matcher.group( 2 );
251 if ( tagList != null )
252 {
253 String[] rawTags = tagList.split( "," );
254 for ( String rawTag : rawTags )
255 {
256 String[] tagParts = rawTag.trim().split( ":" );
257 if ( tagParts.length == 2 && "tag".equals( tagParts[0] ) )
258 {
259 currentChange.addTag( tagParts[1].trim() );
260 }
261 }
262 }
263
264 status = STATUS_GET_AUTHOR;
265 }
266
267
268
269
270
271
272
273 private void processGetAuthor( String line )
274 {
275
276 if ( RAW_TREE_PATTERN.matcher( line ).matches() )
277 {
278 status = STATUS_RAW_TREE;
279 processGetRawTree( line );
280 return;
281 }
282
283 Matcher matcher = AUTHOR_PATTERN.matcher( line );
284 if ( !matcher.matches() )
285 {
286 return;
287 }
288 String author = matcher.group( 1 );
289
290 currentChange.setAuthor( author );
291
292 status = STATUS_GET_DATE;
293 }
294
295
296
297
298
299
300
301 private void processGetRawTree( String line )
302 {
303 if ( !RAW_TREE_PATTERN.matcher( line ).matches() )
304 {
305 return;
306 }
307
308 status = STATUS_RAW_PARENT;
309 }
310
311
312
313
314
315
316
317 private void processGetRawParent( String line )
318 {
319 Matcher matcher = RAW_PARENT_PATTERN.matcher( line );
320 if ( !matcher.matches() )
321 {
322 status = STATUS_RAW_AUTHOR;
323 processGetRawAuthor( line );
324 return;
325 }
326 String parentHash = matcher.group( 1 );
327
328 addParentRevision( parentHash );
329 }
330
331
332
333
334
335
336
337 private void addParentRevision( String hash )
338 {
339 if ( currentChange.getParentRevision() == null )
340 {
341 currentChange.setParentRevision( hash );
342 }
343 else
344 {
345 currentChange.addMergedRevision( hash );
346 }
347 }
348
349
350
351
352
353
354
355 private void processGetRawAuthor( String line )
356 {
357 Matcher matcher = RAW_AUTHOR_PATTERN.matcher( line );
358 if ( !matcher.matches() )
359 {
360 return;
361 }
362 String author = matcher.group( 1 );
363 currentChange.setAuthor( author );
364
365 String datestring = matcher.group( 2 );
366 String tz = matcher.group( 3 );
367
368
369
370 Calendar c = Calendar.getInstance( TimeZone.getTimeZone( tz ) );
371 c.setTimeInMillis( Long.parseLong( datestring ) * 1000 );
372 currentChange.setDate( c.getTime() );
373
374 status = STATUS_RAW_COMMITTER;
375 }
376
377
378
379
380
381
382
383 private void processGetRawCommitter( String line )
384 {
385 if ( !RAW_COMMITTER_PATTERN.matcher( line ).matches() )
386 {
387 return;
388 }
389
390 status = STATUS_GET_COMMENT;
391 }
392
393
394
395
396
397
398
399 private void processGetDate( String line, Locale locale )
400 {
401 Matcher matcher = DATE_PATTERN.matcher( line );
402 if ( !matcher.matches() )
403 {
404 return;
405 }
406
407 String datestring = matcher.group( 1 );
408
409 Date date = parseDate( datestring.trim(), userDateFormat, GIT_TIMESTAMP_PATTERN, locale );
410
411 currentChange.setDate( date );
412
413 status = STATUS_GET_COMMENT;
414 }
415
416
417
418
419
420
421
422 private void processGetComment( String line )
423 {
424 if ( line.length() < 4 )
425 {
426 if ( currentComment == null )
427 {
428 currentComment = new StringBuilder();
429 }
430 else
431 {
432 currentChange.setComment( currentComment.toString() );
433 status = STATUS_GET_FILE;
434 }
435 }
436 else
437 {
438 if ( currentComment.length() > 0 )
439 {
440 currentComment.append( '\n' );
441 }
442
443 currentComment.append( line.substring( 4 ) );
444 }
445 }
446
447
448
449
450
451
452
453
454
455 private void processGetFile( String line )
456 {
457 if ( line.length() == 0 )
458 {
459 if ( currentChange != null )
460 {
461 entries.add( currentChange );
462 }
463
464 resetChangeLog();
465
466 status = STATUS_GET_HEADER;
467 }
468 else
469 {
470 Matcher matcher = FILE_PATTERN.matcher( line );
471 if ( !matcher.matches() )
472 {
473 return;
474 }
475 final String actionChar = matcher.group( 1 );
476
477 final ScmFileStatus action;
478 String name = matcher.group( 2 );
479 String originalName = null;
480 String originalRevision = null;
481 if ( "A".equals( actionChar ) )
482 {
483 action = ScmFileStatus.ADDED;
484 }
485 else if ( "M".equals( actionChar ) )
486 {
487 action = ScmFileStatus.MODIFIED;
488 }
489 else if ( "D".equals( actionChar ) )
490 {
491 action = ScmFileStatus.DELETED;
492 }
493 else if ( "R".equals( actionChar ) )
494 {
495 action = ScmFileStatus.RENAMED;
496 originalName = name;
497 name = matcher.group( 4 );
498 originalRevision = currentChange.getParentRevision();
499 }
500 else if ( "C".equals( actionChar ) )
501 {
502 action = ScmFileStatus.COPIED;
503 originalName = name;
504 name = matcher.group( 4 );
505 originalRevision = currentChange.getParentRevision();
506 }
507 else
508 {
509 action = ScmFileStatus.UNKNOWN;
510 }
511
512 final ChangeFile changeFile = new ChangeFile( name, currentRevision );
513 changeFile.setAction( action );
514 changeFile.setOriginalName( originalName );
515 changeFile.setOriginalRevision( originalRevision );
516 currentChange.addFile( changeFile );
517 }
518 }
519
520 private void resetChangeLog()
521 {
522 currentComment = null;
523 currentChange = null;
524 }
525 }