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