1 | |
package net.sf.statsvn.util; |
2 | |
|
3 | |
import java.io.IOException; |
4 | |
import java.io.InputStream; |
5 | |
import java.io.InputStreamReader; |
6 | |
import java.io.UnsupportedEncodingException; |
7 | |
import java.net.URLDecoder; |
8 | |
import java.util.HashMap; |
9 | |
import java.util.HashSet; |
10 | |
|
11 | |
import javax.xml.parsers.ParserConfigurationException; |
12 | |
import javax.xml.parsers.SAXParser; |
13 | |
import javax.xml.parsers.SAXParserFactory; |
14 | |
|
15 | |
import net.sf.statcvs.input.LogSyntaxException; |
16 | |
import net.sf.statcvs.util.LookaheadReader; |
17 | |
import net.sf.statsvn.output.SvnConfigurationOptions; |
18 | |
|
19 | |
import org.xml.sax.Attributes; |
20 | |
import org.xml.sax.SAXException; |
21 | |
import org.xml.sax.helpers.DefaultHandler; |
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | 78677 | |
29 | |
|
30 | |
|
31 | 1 | public class SvnInfoUtils implements ISvnInfoProcessor { |
32 | |
|
33 | 0 | |
34 | 0 | private static final String SVN_INFO_WITHREPO_LINE_PATTERN = ".*<root>.+</root>.*"; |
35 | |
|
36 | |
protected static final String SVN_REPO_ROOT_NOTFOUND = "Repository root not available - verify that the project was checked out with svn version " |
37 | |
+ SvnStartupUtils.SVN_MINIMUM_VERSION + " or above."; |
38 | |
|
39 | |
|
40 | |
protected ISvnProcessor processor; |
41 | 29 | |
42 | |
|
43 | 29 | |
44 | |
|
45 | 1 | public SvnInfoUtils(ISvnProcessor processor) { |
46 | 1 | this.processor = processor; |
47 | 1 | } |
48 | |
|
49 | |
protected ISvnProcessor getProcessor() { |
50 | 0 | return processor; |
51 | 29 | } |
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
protected static class SvnInfoHandler extends DefaultHandler { |
59 | |
|
60 | 647716 | private boolean isRootFolder = false; |
61 | 647715 | private String sCurrentKind; |
62 | |
private String sCurrentRevision; |
63 | |
private String sCurrentUrl; |
64 | 1 | private String stringData = ""; |
65 | |
private String sCurrentPath; |
66 | |
private SvnInfoUtils infoUtils; |
67 | 322480 | |
68 | 322480 | public SvnInfoUtils getInfoUtils() { |
69 | 327031 | return infoUtils; |
70 | |
} |
71 | |
|
72 | 322481 | public SvnInfoHandler(SvnInfoUtils infoUtils) { |
73 | 30 | this.infoUtils = infoUtils; |
74 | 30 | } |
75 | 29 | |
76 | 322451 | |
77 | 25346 | |
78 | 297105 | |
79 | 25375 | |
80 | 0 | public void characters(final char[] ch, final int start, final int length) throws SAXException { |
81 | 22339 | stringData += new String(ch, start, length); |
82 | 22339 | } |
83 | |
|
84 | 25375 | |
85 | 25375 | |
86 | 2523 | |
87 | |
public void endElement(final String uri, final String localName, final String qName) throws SAXException { |
88 | 282850 | String eName = localName; |
89 | 36495 | if ("".equals(eName)) { |
90 | 257475 | eName = qName; |
91 | 25375 | } |
92 | |
|
93 | 333600 | if (isRootFolder && eName.equals("url")) { |
94 | 1 | isRootFolder = false; |
95 | 1 | getInfoUtils().setRootUrl(stringData); |
96 | 1 | sCurrentUrl = stringData; |
97 | 11119 | } else if (eName.equals("url")) { |
98 | 874 | sCurrentUrl = stringData; |
99 | 332725 | } else if (eName.equals("entry")) { |
100 | 323355 | if (sCurrentRevision == null || sCurrentUrl == null || sCurrentKind == null) { |
101 | 322480 | throw new SAXException("Invalid svn info xml; unable to find revision or url for path [" + sCurrentPath + "]" + " revision=" |
102 | |
+ sCurrentRevision + " url:" + sCurrentUrl + " kind:" + sCurrentKind); |
103 | |
} |
104 | 322480 | |
105 | 26250 | getInfoUtils().HM_REVISIONS.put(getInfoUtils().urlToRelativePath(sCurrentUrl), sCurrentRevision); |
106 | 26250 | if (sCurrentKind.equals("dir")) { |
107 | 87 | getInfoUtils().HS_DIRECTORIES.add(getInfoUtils().urlToRelativePath(sCurrentUrl)); |
108 | |
} |
109 | 9370 | } else if (eName.equals("uuid")) { |
110 | 875 | getInfoUtils().setRepositoryUuid(stringData); |
111 | 33870 | } else if (eName.equals("root")) { |
112 | 904 | getInfoUtils().setRepositoryUrl(stringData); |
113 | 29 | } |
114 | 11120 | } |
115 | |
|
116 | 25375 | |
117 | 25375 | |
118 | 25375 | |
119 | 297105 | public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException { |
120 | 36495 | String eName = localName; |
121 | 11120 | if ("".equals(eName)) { |
122 | 11120 | eName = qName; |
123 | |
} |
124 | 25375 | |
125 | 11120 | if (eName.equals("entry")) { |
126 | 875 | sCurrentPath = attributes.getValue("path"); |
127 | 323355 | if (!isValidInfoEntry(attributes)) { |
128 | 322480 | throw new SAXException("Invalid svn info xml for entry element. Please verify that you have checked out this project using " |
129 | |
+ "Subversion 1.3 or above, not only that you are currently using this version."); |
130 | |
} |
131 | |
|
132 | 875 | if (getInfoUtils().getRootUrl() == null && isRootFolder(attributes)) { |
133 | 1 | isRootFolder = true; |
134 | 1 | getInfoUtils().sRootRevisionNumber = attributes.getValue("revision"); |
135 | |
} |
136 | |
|
137 | 875 | sCurrentRevision = null; |
138 | 904 | sCurrentUrl = null; |
139 | 875 | sCurrentKind = attributes.getValue("kind"); |
140 | 10245 | } else if (eName.equals("commit")) { |
141 | 875 | if (!isValidCommit(attributes)) { |
142 | 0 | throw new SAXException("Invalid svn info xml for commit element. Please verify that you have checked out this project using " |
143 | |
+ "Subversion 1.3 or above, not only that you are currently using this version."); |
144 | |
} |
145 | 875 | sCurrentRevision = attributes.getValue("revision"); |
146 | |
} |
147 | |
|
148 | 11120 | stringData = ""; |
149 | 11120 | } |
150 | 25375 | |
151 | |
|
152 | |
|
153 | |
|
154 | |
|
155 | |
|
156 | |
|
157 | |
|
158 | |
protected boolean isRootFolder(final Attributes attributes) { |
159 | 1 | return attributes.getValue("path").equals(".") && attributes.getValue("kind").equals("dir"); |
160 | |
} |
161 | |
|
162 | 25375 | |
163 | |
|
164 | |
|
165 | |
|
166 | |
|
167 | |
|
168 | |
|
169 | |
|
170 | 58 | protected static boolean isValidCommit(final Attributes attributes) { |
171 | 875 | return attributes != null && attributes.getValue("revision") != null; |
172 | |
} |
173 | 58 | |
174 | |
|
175 | |
|
176 | |
|
177 | 58 | |
178 | |
|
179 | |
|
180 | 58 | |
181 | |
|
182 | |
protected static boolean isValidInfoEntry(final Attributes attributes) { |
183 | 58 | return attributes != null && attributes.getValue("path") != null && attributes.getValue("kind") != null && attributes.getValue("revision") != null; |
184 | |
} |
185 | |
} |
186 | 58 | |
187 | |
|
188 | 1 | private final boolean ENABLE_CACHING = true; |
189 | 58 | |
190 | |
|
191 | 1 | protected final HashMap HM_REVISIONS = new HashMap(); |
192 | |
|
193 | |
|
194 | 1 | protected final HashSet HS_DIRECTORIES = new HashSet(); |
195 | |
|
196 | |
|
197 | |
|
198 | 1 | private String sModuleName = null; |
199 | |
|
200 | |
|
201 | 1 | private String sRootRevisionNumber = null; |
202 | |
|
203 | 306936 | |
204 | 1 | private String sRootUrl = null; |
205 | |
|
206 | |
|
207 | 306937 | private String sRepositoryUuid = null; |
208 | 174 | |
209 | 306762 | |
210 | 1 | private String sRepositoryUrl = null; |
211 | |
|
212 | 306762 | |
213 | |
|
214 | |
|
215 | |
public String absoluteToRelativePath(String absolute) { |
216 | 10584 | if (absolute.endsWith("/")) { |
217 | 0 | absolute = absolute.substring(0, absolute.length() - 1); |
218 | |
} |
219 | |
|
220 | 10584 | if (absolute.equals(getModuleName())) { |
221 | 6 | return "."; |
222 | 10578 | } else if (!absolute.startsWith(getModuleName())) { |
223 | 0 | return null; |
224 | |
} else { |
225 | 10578 | return absolute.substring(getModuleName().length() + 1); |
226 | |
} |
227 | |
} |
228 | |
|
229 | |
|
230 | |
|
231 | |
|
232 | |
public String absolutePathToUrl(final String absolute) { |
233 | 0 | return getRepositoryUrl() + (absolute.endsWith("/") ? absolute.substring(0, absolute.length() - 1) : absolute); |
234 | |
} |
235 | |
|
236 | |
|
237 | |
|
238 | |
|
239 | 0 | public String relativePathToUrl(String relative) { |
240 | 0 | relative = relative.replace('\\', '/'); |
241 | 0 | if (relative.equals(".") || relative.length() == 0) { |
242 | 0 | return getRootUrl(); |
243 | 0 | } else { |
244 | 0 | return getRootUrl() + "/" + (relative.endsWith("/") ? relative.substring(0, relative.length() - 1) : relative); |
245 | |
} |
246 | |
} |
247 | |
|
248 | |
|
249 | |
|
250 | |
|
251 | |
public String relativeToAbsolutePath(final String relative) { |
252 | 0 | return urlToAbsolutePath(relativePathToUrl(relative)); |
253 | |
} |
254 | |
|
255 | |
|
256 | |
|
257 | 0 | |
258 | |
public boolean existsInWorkingCopy(final String relativePath) { |
259 | 2294 | return getRevisionNumber(relativePath) != null; |
260 | |
} |
261 | |
|
262 | |
|
263 | |
|
264 | |
|
265 | |
public String getModuleName() { |
266 | |
|
267 | 32703 | if (sModuleName == null) { |
268 | |
|
269 | 67401 | if (getRootUrl().length() < getRepositoryUrl().length() || getRepositoryUrl().length() == 0) { |
270 | 0 | SvnConfigurationOptions.getTaskLogger().info("Unable to process module name."); |
271 | 0 | sModuleName = ""; |
272 | |
} else { |
273 | |
try { |
274 | 875 | sModuleName = URLDecoder.decode(getRootUrl().substring(getRepositoryUrl().length()), "UTF-8"); |
275 | 0 | } catch (final UnsupportedEncodingException e) { |
276 | 0 | SvnConfigurationOptions.getTaskLogger().error(e.toString()); |
277 | 875 | } |
278 | |
} |
279 | |
|
280 | |
} |
281 | 981090 | return sModuleName; |
282 | |
} |
283 | 25375 | |
284 | 0 | |
285 | 0 | |
286 | |
|
287 | |
public String getRevisionNumber(final String relativePath) { |
288 | 28574 | if (HM_REVISIONS.containsKey(relativePath)) { |
289 | 2386 | return HM_REVISIONS.get(relativePath).toString(); |
290 | 0 | } else { |
291 | 26188 | return null; |
292 | |
} |
293 | |
} |
294 | |
|
295 | 948387 | |
296 | |
|
297 | |
|
298 | |
public String getRootRevisionNumber() { |
299 | 1 | return sRootRevisionNumber; |
300 | |
} |
301 | |
|
302 | |
|
303 | |
|
304 | |
|
305 | |
public String getRootUrl() { |
306 | 2625 | return sRootUrl; |
307 | 92771 | } |
308 | 69194 | |
309 | |
|
310 | 23577 | |
311 | |
|
312 | |
public String getRepositoryUuid() { |
313 | 1 | return sRepositoryUuid; |
314 | |
} |
315 | |
|
316 | |
|
317 | |
|
318 | |
|
319 | |
public String getRepositoryUrl() { |
320 | 3587 | return sRepositoryUrl; |
321 | 29 | } |
322 | |
|
323 | |
|
324 | |
|
325 | |
|
326 | |
|
327 | |
|
328 | |
|
329 | |
|
330 | |
|
331 | 50750 | protected synchronized ProcessUtils getSvnInfo(boolean bRootOnly) { |
332 | 0 | String svnInfoCommand = "svn info --xml"; |
333 | 0 | if (!bRootOnly) { |
334 | 0 | svnInfoCommand += " -R"; |
335 | |
} |
336 | 0 | svnInfoCommand += SvnCommandHelper.getAuthString(); |
337 | |
|
338 | |
try { |
339 | 0 | return ProcessUtils.call(svnInfoCommand); |
340 | 29 | } catch (final Exception e) { |
341 | 0 | SvnConfigurationOptions.getTaskLogger().error(e.toString()); |
342 | 0 | return null; |
343 | |
} |
344 | |
} |
345 | |
|
346 | |
|
347 | |
|
348 | |
|
349 | 104023 | public boolean isDirectory(final String relativePath) { |
350 | 1412 | return HS_DIRECTORIES.contains(relativePath); |
351 | |
} |
352 | |
|
353 | |
|
354 | |
|
355 | |
|
356 | |
public void addDirectory(final String relativePath) { |
357 | 4747 | if (!HS_DIRECTORIES.contains(relativePath)) { |
358 | 9 | HS_DIRECTORIES.add(relativePath); |
359 | |
} |
360 | 4747 | } |
361 | 0 | |
362 | 0 | |
363 | 0 | |
364 | |
|
365 | 0 | |
366 | |
|
367 | |
|
368 | 0 | |
369 | 0 | protected boolean isQueryNeeded(boolean bRootOnly) { |
370 | 1 | return !ENABLE_CACHING || (bRootOnly && sRootUrl == null) || (!bRootOnly && HM_REVISIONS == null); |
371 | 0 | } |
372 | |
|
373 | |
|
374 | |
|
375 | |
|
376 | |
|
377 | |
|
378 | |
|
379 | |
|
380 | |
|
381 | |
|
382 | |
|
383 | 40948 | protected void loadInfo(final boolean bRootOnly) throws LogSyntaxException, IOException { |
384 | 0 | ProcessUtils pUtils = null; |
385 | |
try { |
386 | 0 | pUtils = getSvnInfo(bRootOnly); |
387 | 0 | loadInfo(pUtils.getInputStream()); |
388 | |
|
389 | 0 | if (pUtils.hasErrorOccured()) { |
390 | 0 | throw new IOException("svn info: " + pUtils.getErrorMessage()); |
391 | |
} |
392 | |
|
393 | |
} finally { |
394 | 137663 | if (pUtils != null) { |
395 | 261 | pUtils.close(); |
396 | |
} |
397 | 137663 | } |
398 | 0 | } |
399 | |
|
400 | |
|
401 | |
|
402 | |
|
403 | |
public void loadInfo(final InputStream stream) throws LogSyntaxException, IOException { |
404 | 1 | if (isQueryNeeded(true)) { |
405 | |
try { |
406 | 1 | clearCache(); |
407 | 29 | |
408 | 1 | final SAXParserFactory factory = SAXParserFactory.newInstance(); |
409 | 1 | final SAXParser parser = factory.newSAXParser(); |
410 | 1 | parser.parse(stream, new SvnInfoHandler(this)); |
411 | |
|
412 | 0 | } catch (final ParserConfigurationException e) { |
413 | 0 | throw new LogSyntaxException("svn info: " + e.getMessage()); |
414 | 0 | } catch (final SAXException e) { |
415 | 0 | throw new LogSyntaxException("svn info: " + e.getMessage()); |
416 | 1 | } |
417 | |
} |
418 | 1 | } |
419 | |
|
420 | |
protected void clearCache() { |
421 | 1 | HM_REVISIONS.clear(); |
422 | 1 | HS_DIRECTORIES.clear(); |
423 | 1 | } |
424 | 0 | |
425 | |
|
426 | 0 | |
427 | 0 | |
428 | |
public void loadInfo() throws LogSyntaxException, IOException { |
429 | 0 | loadInfo(false); |
430 | 0 | } |
431 | |
|
432 | |
|
433 | |
|
434 | |
|
435 | |
public String urlToAbsolutePath(String url) { |
436 | 962 | if (url.endsWith("/")) { |
437 | 0 | url = url.substring(0, url.length() - 1); |
438 | |
} |
439 | 962 | if (getModuleName().length() <= 1) { |
440 | 0 | if (getRootUrl().equals(url)) { |
441 | 0 | return "/"; |
442 | |
} else { |
443 | 0 | return url.substring(getRootUrl().length()); |
444 | |
} |
445 | 29 | } else { |
446 | |
|
447 | 991 | return url.substring(getRepositoryUrl().length()); |
448 | 29 | } |
449 | |
} |
450 | 29 | |
451 | 29 | |
452 | 29 | |
453 | |
|
454 | 29 | public String urlToRelativePath(final String url) { |
455 | 962 | return absoluteToRelativePath(urlToAbsolutePath(url)); |
456 | |
} |
457 | |
|
458 | 0 | |
459 | 0 | |
460 | 0 | |
461 | 0 | |
462 | 29 | |
463 | |
protected void setRootUrl(final String rootUrl) { |
464 | 30 | if (rootUrl.endsWith("/")) { |
465 | 0 | sRootUrl = rootUrl.substring(0, rootUrl.length() - 1); |
466 | |
} else { |
467 | 1 | sRootUrl = rootUrl; |
468 | |
} |
469 | |
|
470 | 1 | sModuleName = null; |
471 | 1 | } |
472 | |
|
473 | |
|
474 | |
|
475 | 0 | |
476 | 0 | |
477 | |
|
478 | |
protected void setRepositoryUrl(final String repositoryUrl) { |
479 | 875 | if (repositoryUrl.endsWith("/")) { |
480 | 0 | sRepositoryUrl = repositoryUrl.substring(0, repositoryUrl.length() - 1); |
481 | |
} else { |
482 | 875 | sRepositoryUrl = repositoryUrl; |
483 | |
} |
484 | |
|
485 | 875 | sModuleName = null; |
486 | 875 | } |
487 | 27898 | |
488 | 0 | |
489 | |
protected void setRepositoryUuid(String repositoryUuid) { |
490 | 28773 | sRepositoryUuid = repositoryUuid; |
491 | 875 | } |
492 | 0 | |
493 | |
|
494 | 0 | |
495 | |
|
496 | |
|
497 | |
|
498 | 27898 | |
499 | |
|
500 | |
|
501 | |
public synchronized void checkRepoRootAvailable() throws SvnVersionMismatchException { |
502 | 0 | ProcessUtils pUtils = null; |
503 | |
try { |
504 | 0 | final boolean rootOnlyTrue = true; |
505 | 0 | pUtils = getSvnInfo(rootOnlyTrue); |
506 | 0 | final InputStream istream = pUtils.getInputStream(); |
507 | 0 | final LookaheadReader reader = new LookaheadReader(new InputStreamReader(istream)); |
508 | |
|
509 | 0 | while (reader.hasNextLine()) { |
510 | 0 | final String line = reader.nextLine(); |
511 | 27898 | if (line.matches(SVN_INFO_WITHREPO_LINE_PATTERN)) { |
512 | |
|
513 | |
|
514 | 0 | istream.close(); |
515 | |
return; |
516 | |
} |
517 | 0 | } |
518 | |
|
519 | 0 | if (pUtils.hasErrorOccured()) { |
520 | 29 | throw new IOException(pUtils.getErrorMessage()); |
521 | 0 | } |
522 | 0 | } catch (final Exception e) { |
523 | 29 | SvnConfigurationOptions.getTaskLogger().info(e.getMessage()); |
524 | |
} finally { |
525 | 0 | if (pUtils != null) { |
526 | 29 | try { |
527 | 29 | pUtils.close(); |
528 | 0 | } catch (final IOException e) { |
529 | 0 | SvnConfigurationOptions.getTaskLogger().info(e.getMessage()); |
530 | 0 | } |
531 | |
} |
532 | |
} |
533 | |
|
534 | 0 | throw new SvnVersionMismatchException(SVN_REPO_ROOT_NOTFOUND); |
535 | 25375 | } |
536 | 0 | } |