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