1 package org.apache.maven.scm.provider.git.gitexe.command.status;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.UnsupportedEncodingException;
24 import java.net.URI;
25 import java.net.URISyntaxException;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.maven.scm.ScmFile;
33 import org.apache.maven.scm.ScmFileStatus;
34 import org.apache.maven.scm.ScmFileSet;
35 import org.apache.maven.scm.log.ScmLogger;
36 import org.codehaus.plexus.util.cli.StreamConsumer;
37
38
39
40
41 public class GitStatusConsumer
42 implements StreamConsumer
43 {
44
45
46
47
48 private static final Pattern ADDED_PATTERN = Pattern.compile( "^A[ M]* (.*)$" );
49
50
51
52
53 private static final Pattern MODIFIED_PATTERN = Pattern.compile( "^ *M[ M]* (.*)$" );
54
55
56
57
58 private static final Pattern DELETED_PATTERN = Pattern.compile( "^ *D * (.*)$" );
59
60
61
62
63 private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R (.*) -> (.*)$" );
64
65 private ScmLogger logger;
66
67 private File workingDirectory;
68
69 private ScmFileSet scmFileSet;
70
71
72
73
74 private List<ScmFile> changedFiles = new ArrayList<ScmFile>();
75
76 private URI relativeRepositoryPath;
77
78
79
80
81
82
83
84
85
86
87
88 public GitStatusConsumer( ScmLogger logger, File workingDirectory )
89 {
90 this.logger = logger;
91 this.workingDirectory = workingDirectory;
92 }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public GitStatusConsumer( ScmLogger logger, File workingDirectory, URI relativeRepositoryPath )
108 {
109 this( logger, workingDirectory );
110 this.relativeRepositoryPath = relativeRepositoryPath;
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 public GitStatusConsumer( ScmLogger logger, File workingDirectory, ScmFileSet scmFileSet )
127 {
128 this( logger, workingDirectory );
129 this.scmFileSet = scmFileSet;
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 public GitStatusConsumer( ScmLogger logger, File workingDirectory, URI relativeRepositoryPath,
147 ScmFileSet scmFileSet )
148 {
149 this( logger, workingDirectory, scmFileSet );
150 this.relativeRepositoryPath = relativeRepositoryPath;
151 }
152
153
154
155
156
157
158
159
160 public void consumeLine( String line )
161 {
162 if ( logger.isDebugEnabled() )
163 {
164 logger.debug( line );
165 }
166 if ( StringUtils.isEmpty( line ) )
167 {
168 return;
169 }
170
171 ScmFileStatus status = null;
172
173 List<String> files = new ArrayList<String>();
174
175 Matcher matcher;
176 if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() )
177 {
178 status = ScmFileStatus.ADDED;
179 files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
180 }
181 else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() )
182 {
183 status = ScmFileStatus.MODIFIED;
184 files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
185 }
186 else if ( ( matcher = DELETED_PATTERN.matcher( line ) ).find() )
187 {
188 status = ScmFileStatus.DELETED;
189 files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
190 }
191 else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() )
192 {
193 status = ScmFileStatus.RENAMED;
194 files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
195 files.add( resolvePath( matcher.group( 2 ), relativeRepositoryPath ) );
196 logger.debug( "RENAMED status for line '" + line + "' files added '" + matcher.group( 1 ) + "' '"
197 + matcher.group( 2 ) );
198 }
199 else
200 {
201 logger.warn( "Ignoring unrecognized line: " + line );
202 return;
203 }
204
205
206 if ( !files.isEmpty() && status != null )
207 {
208 if ( workingDirectory != null )
209 {
210 if ( status == ScmFileStatus.RENAMED )
211 {
212 String oldFilePath = files.get( 0 );
213 String newFilePath = files.get( 1 );
214 if ( isFile( oldFilePath ) )
215 {
216 logger.debug( "file '" + oldFilePath + "' is a file" );
217 return;
218 }
219 else
220 {
221 logger.debug( "file '" + oldFilePath + "' not a file" );
222 }
223 if ( !isFile( newFilePath ) )
224 {
225 logger.debug( "file '" + newFilePath + "' not a file" );
226 return;
227 }
228 else
229 {
230 logger.debug( "file '" + newFilePath + "' is a file" );
231 }
232 }
233 else if ( status == ScmFileStatus.DELETED )
234 {
235 if ( isFile( files.get( 0 ) ) )
236 {
237 return;
238 }
239 }
240 else
241 {
242 if ( !isFile( files.get( 0 ) ) )
243 {
244 return;
245 }
246 }
247 }
248
249 for ( String file : files )
250 {
251 if ( this.scmFileSet != null && !isFileNameInFileList( this.scmFileSet.getFileList(), file ) )
252 {
253
254 }
255 else
256 {
257 changedFiles.add( new ScmFile( file, status ) );
258 }
259 }
260 }
261 }
262
263 private boolean isFileNameInFileList( List<File> fileList, String fileName )
264 {
265 if ( relativeRepositoryPath == null )
266 {
267 return fileList.contains( new File( fileName ) );
268 }
269 else
270 {
271 for ( File f : fileList )
272 {
273 File file = new File( relativeRepositoryPath.getPath(), fileName );
274 if ( file.getPath().endsWith( f.getName() ) )
275 {
276 return true;
277 }
278 }
279 return fileList.isEmpty();
280 }
281
282 }
283
284 private boolean isFile( String file )
285 {
286 File targetFile = new File( workingDirectory, file );
287 return targetFile.isFile();
288 }
289
290 protected static String resolvePath( String fileEntry, URI path )
291 {
292
293 String cleanedEntry = stripQuotes( fileEntry );
294 if ( path != null )
295 {
296 return resolveURI( cleanedEntry, path ).getPath();
297 }
298 else
299 {
300 return cleanedEntry;
301 }
302 }
303
304
305
306
307
308
309
310 public static URI resolveURI( String fileEntry, URI path )
311 {
312
313
314
315 return path.relativize( uriFromPath( stripQuotes ( fileEntry ) ) );
316 }
317
318
319
320
321
322
323
324
325 public static URI uriFromPath( String path )
326 {
327 try
328 {
329 if ( path != null && path.indexOf( ':' ) != -1 )
330 {
331
332 String tmp = new URI( null, null, "/x" + path, null ).toString().substring( 2 );
333
334 return new URI( tmp.replace( ":", "%3A" ) );
335 }
336 else
337 {
338 return new URI( null, null, path, null );
339 }
340 }
341 catch ( URISyntaxException x )
342 {
343 throw new IllegalArgumentException( x.getMessage(), x );
344 }
345 }
346
347 public List<ScmFile> getChangedFiles()
348 {
349 return changedFiles;
350 }
351
352
353
354
355
356 private static String stripQuotes( String str )
357 {
358 int strLen = str.length();
359 return ( strLen > 0 && str.startsWith( "\"" ) && str.endsWith( "\"" ) )
360 ? unescape( str.substring( 1, strLen - 1 ) )
361 : str;
362 }
363
364
365
366
367
368
369
370 private static String unescape( String fileEntry )
371 {
372
373 int pos = fileEntry.indexOf( '\\' );
374 if ( pos == -1 )
375 {
376 return fileEntry;
377 }
378
379
380 byte[] inba = fileEntry.getBytes();
381 int inSub = 0;
382 byte[] outba = new byte[fileEntry.length()];
383 int outSub = 0;
384
385 while ( true )
386 {
387 System.arraycopy( inba, inSub, outba, outSub, pos - inSub );
388 outSub += pos - inSub;
389 inSub = pos + 1;
390 switch ( (char) inba[inSub++] )
391 {
392 case '"':
393 outba[outSub++] = '"';
394 break;
395
396 case 'a':
397 outba[outSub++] = 7;
398 break;
399
400 case 'b':
401 outba[outSub++] = '\b';
402 break;
403
404 case 't':
405 outba[outSub++] = '\t';
406 break;
407
408 case 'n':
409 outba[outSub++] = '\n';
410 break;
411
412 case 'v':
413 outba[outSub++] = 11;
414 break;
415
416 case 'f':
417 outba[outSub++] = '\f';
418 break;
419
420 case 'r':
421 outba[outSub++] = '\f';
422 break;
423
424 case '\\':
425 outba[outSub++] = '\\';
426 break;
427
428 case '0':
429 case '1':
430 case '2':
431 case '3':
432
433 byte b = (byte) ( ( inba[inSub - 1] - '0' ) << 6 );
434 b |= (byte) ( ( inba[inSub++] - '0' ) << 3 );
435 b |= (byte) ( inba[inSub++] - '0' );
436 outba[outSub++] = b;
437 break;
438
439 default:
440
441 outba[outSub++] = '\\';
442 inSub--;
443 break;
444 }
445 pos = fileEntry.indexOf( '\\', inSub );
446 if ( pos == -1 )
447 {
448 System.arraycopy( inba, inSub, outba, outSub, inba.length - inSub );
449 outSub += inba.length - inSub;
450 break;
451 }
452 }
453 try
454 {
455
456 return new String( outba, 0, outSub, "UTF-8" );
457 }
458 catch ( UnsupportedEncodingException e )
459 {
460 throw new RuntimeException( e );
461 }
462 }
463 }