| | 56 | /** |
|---|
| | 57 | * @author bastian |
|---|
| | 58 | * |
|---|
| | 59 | */ |
|---|
| | 60 | private static final class ChangesetContentHandler implements |
|---|
| | 61 | ContentHandler { |
|---|
| | 62 | |
|---|
| | 63 | private String br; |
|---|
| | 64 | private String tg; |
|---|
| | 65 | private int rv; |
|---|
| | 66 | private String ns; |
|---|
| | 67 | private String nl; |
|---|
| | 68 | private String di; |
|---|
| | 69 | private String da; |
|---|
| | 70 | private String au; |
|---|
| | 71 | private String pr; |
|---|
| | 72 | private String de; |
|---|
| | 73 | private static IResource res; |
|---|
| | 74 | private Direction direction; |
|---|
| | 75 | private HgRepositoryLocation repository; |
|---|
| | 76 | private File bundleFile; |
|---|
| | 77 | private File hgRoot; |
|---|
| | 78 | private static Map<IPath, SortedSet<ChangeSet>> fileRevisions; |
|---|
| | 79 | private Set<String> filesModified = new TreeSet<String>(); |
|---|
| | 80 | private Set<String> filesAdded = new TreeSet<String>(); |
|---|
| | 81 | private Set<String> filesRemoved = new TreeSet<String>(); |
|---|
| | 82 | private Action action; |
|---|
| | 83 | |
|---|
| | 84 | /** |
|---|
| | 85 | * @param res |
|---|
| | 86 | * @param direction |
|---|
| | 87 | * @param repository |
|---|
| | 88 | * @param bundleFile |
|---|
| | 89 | * @param hgRoot |
|---|
| | 90 | * @param fileRevisions |
|---|
| | 91 | */ |
|---|
| | 92 | public ChangesetContentHandler(IResource res, Direction direction, |
|---|
| | 93 | HgRepositoryLocation repository, File bundleFile, File hgRoot, |
|---|
| | 94 | Map<IPath, SortedSet<ChangeSet>> fileRevisions) { |
|---|
| | 95 | ChangesetContentHandler.res = res; |
|---|
| | 96 | this.direction = direction; |
|---|
| | 97 | this.repository = repository; |
|---|
| | 98 | this.bundleFile = bundleFile; |
|---|
| | 99 | this.hgRoot = hgRoot; |
|---|
| | 100 | ChangesetContentHandler.fileRevisions = fileRevisions; |
|---|
| | 101 | } |
|---|
| | 102 | |
|---|
| | 103 | private static String unescape(String string) { |
|---|
| | 104 | return string.replaceAll("<", "<").replaceAll(">", ">") |
|---|
| | 105 | .replaceAll("&", "&"); |
|---|
| | 106 | } |
|---|
| | 107 | |
|---|
| | 108 | /** |
|---|
| | 109 | * Remove a leading tab on each line in the string. |
|---|
| | 110 | * |
|---|
| | 111 | * @param string |
|---|
| | 112 | * @return |
|---|
| | 113 | */ |
|---|
| | 114 | private static String untab(String string) { |
|---|
| | 115 | return string.replaceAll("\n\t", "\n"); |
|---|
| | 116 | } |
|---|
| | 117 | |
|---|
| | 118 | private static String[] splitClean(String string, String sep) { |
|---|
| | 119 | if (string == null || string.length() == 0) { |
|---|
| | 120 | return new String[] {}; |
|---|
| | 121 | } |
|---|
| | 122 | return string.split(sep); |
|---|
| | 123 | } |
|---|
| | 124 | |
|---|
| | 125 | public void characters(char[] ch, int start, int length) |
|---|
| | 126 | throws SAXException { |
|---|
| | 127 | } |
|---|
| | 128 | |
|---|
| | 129 | public void endDocument() throws SAXException { |
|---|
| | 130 | } |
|---|
| | 131 | |
|---|
| | 132 | public void endElement(String uri, String localName, String name) |
|---|
| | 133 | throws SAXException { |
|---|
| | 134 | if (name.equals("cs")) { |
|---|
| | 135 | |
|---|
| | 136 | ChangeSet.Builder csb = new ChangeSet.Builder(rv, nl, br, di, |
|---|
| | 137 | au); |
|---|
| | 138 | csb.tag(tg); |
|---|
| | 139 | csb.nodeShort(ns); |
|---|
| | 140 | csb.ageDate(da); |
|---|
| | 141 | csb.description(untab(unescape(de))); |
|---|
| | 142 | csb.parents(splitClean(pr, " ")); |
|---|
| | 143 | |
|---|
| | 144 | csb.hgRoot(hgRoot).bundleFile(bundleFile) |
|---|
| | 145 | .repository(repository).direction(direction); |
|---|
| | 146 | |
|---|
| | 147 | csb.bundleFile(bundleFile); |
|---|
| | 148 | csb.direction(direction); |
|---|
| | 149 | csb.hgRoot(hgRoot); |
|---|
| | 150 | csb.repository(repository); |
|---|
| | 151 | |
|---|
| | 152 | List<FileStatus> list = new ArrayList<FileStatus>(); |
|---|
| | 153 | for (String file : filesModified) { |
|---|
| | 154 | list.add(new FileStatus(FileStatus.Action.MODIFIED, file)); |
|---|
| | 155 | } |
|---|
| | 156 | for (String file : filesAdded) { |
|---|
| | 157 | list.add(new FileStatus(FileStatus.Action.ADDED, file)); |
|---|
| | 158 | } |
|---|
| | 159 | for (String file : filesRemoved) { |
|---|
| | 160 | list.add(new FileStatus(FileStatus.Action.REMOVED, file)); |
|---|
| | 161 | } |
|---|
| | 162 | csb.changedFiles(list.toArray(new FileStatus[list.size()])); |
|---|
| | 163 | |
|---|
| | 164 | ChangeSet changeSet = csb.build(); |
|---|
| | 165 | |
|---|
| | 166 | // changeset to resources & project |
|---|
| | 167 | try { |
|---|
| | 168 | addChangesetToResourceMap(changeSet); |
|---|
| | 169 | } catch (HgException e) { |
|---|
| | 170 | throw new SAXException(e); |
|---|
| | 171 | } |
|---|
| | 172 | filesModified.clear(); |
|---|
| | 173 | filesAdded.clear(); |
|---|
| | 174 | filesRemoved.clear(); |
|---|
| | 175 | } |
|---|
| | 176 | } |
|---|
| | 177 | |
|---|
| | 178 | public void endPrefixMapping(String prefix) throws SAXException { |
|---|
| | 179 | } |
|---|
| | 180 | |
|---|
| | 181 | public void ignorableWhitespace(char[] ch, int start, int length) |
|---|
| | 182 | throws SAXException { |
|---|
| | 183 | } |
|---|
| | 184 | |
|---|
| | 185 | public void processingInstruction(String target, String data) |
|---|
| | 186 | throws SAXException { |
|---|
| | 187 | } |
|---|
| | 188 | |
|---|
| | 189 | public void setDocumentLocator(Locator locator) { |
|---|
| | 190 | } |
|---|
| | 191 | |
|---|
| | 192 | public void skippedEntity(String name) throws SAXException { |
|---|
| | 193 | } |
|---|
| | 194 | |
|---|
| | 195 | public void startDocument() throws SAXException { |
|---|
| | 196 | } |
|---|
| | 197 | |
|---|
| | 198 | public void startElement(String uri, String localName, String name, |
|---|
| | 199 | Attributes atts) throws SAXException { |
|---|
| | 200 | /* |
|---|
| | 201 | * <br v="{branches}"/> <tg v="{tags}"/> <rv v="{rev}"/> <ns |
|---|
| | 202 | * v="{node|short}"/> <nl v="{node}"/> <di v="{date|isodate}"/> <da |
|---|
| | 203 | * v="{date|age}"/> <au v="{author|person}"/> <pr v="{parents}"/> |
|---|
| | 204 | * <de v="{desc|escape|tabindent}"/> |
|---|
| | 205 | */ |
|---|
| | 206 | if (name.equals("br")) { |
|---|
| | 207 | this.br = atts.getValue(0); |
|---|
| | 208 | } else if (name.equals("tg")) { |
|---|
| | 209 | this.tg = atts.getValue(0); |
|---|
| | 210 | } else if (name.equals("rv")) { |
|---|
| | 211 | this.rv = Integer.parseInt(atts.getValue(0)); |
|---|
| | 212 | } else if (name.equals("ns")) { |
|---|
| | 213 | this.ns = atts.getValue(0); |
|---|
| | 214 | } else if (name.equals("nl")) { |
|---|
| | 215 | this.nl = atts.getValue(0); |
|---|
| | 216 | } else if (name.equals("di")) { |
|---|
| | 217 | this.di = atts.getValue(0); |
|---|
| | 218 | } else if (name.equals("da")) { |
|---|
| | 219 | this.da = atts.getValue(0); |
|---|
| | 220 | } else if (name.equals("au")) { |
|---|
| | 221 | this.au = atts.getValue(0); |
|---|
| | 222 | } else if (name.equals("pr")) { |
|---|
| | 223 | this.pr = atts.getValue(0); |
|---|
| | 224 | } else if (name.equals("de")) { |
|---|
| | 225 | this.de = untab(unescape(atts.getValue(0))); |
|---|
| | 226 | } else if (name.equals("fl")) { |
|---|
| | 227 | this.action = FileStatus.Action.MODIFIED; |
|---|
| | 228 | } else if (name.equals("fa")) { |
|---|
| | 229 | this.action = FileStatus.Action.ADDED; |
|---|
| | 230 | } else if (name.equals("fd")) { |
|---|
| | 231 | this.action = FileStatus.Action.REMOVED; |
|---|
| | 232 | } else if (name.equals("f")) { |
|---|
| | 233 | if (this.action == Action.ADDED) { |
|---|
| | 234 | filesAdded.add(atts.getValue(0)); |
|---|
| | 235 | filesModified.remove(atts.getValue(0)); |
|---|
| | 236 | } else if (this.action == Action.MODIFIED) { |
|---|
| | 237 | filesModified.add(atts.getValue(0)); |
|---|
| | 238 | } else if (this.action == Action.REMOVED) { |
|---|
| | 239 | filesRemoved.add(atts.getValue(0)); |
|---|
| | 240 | filesModified.remove(atts.getValue(0)); |
|---|
| | 241 | } |
|---|
| | 242 | } |
|---|
| | 243 | } |
|---|
| | 244 | |
|---|
| | 245 | public void startPrefixMapping(String prefix, String uri) |
|---|
| | 246 | throws SAXException { |
|---|
| | 247 | } |
|---|
| | 248 | |
|---|
| | 249 | /** |
|---|
| | 250 | * @param proj |
|---|
| | 251 | * @param fileRevisions |
|---|
| | 252 | * @param cs |
|---|
| | 253 | * @throws HgException |
|---|
| | 254 | * @throws IOException |
|---|
| | 255 | */ |
|---|
| | 256 | private final static void addChangesetToResourceMap(ChangeSet cs) |
|---|
| | 257 | throws HgException { |
|---|
| | 258 | try { |
|---|
| | 259 | if (cs.getChangedFiles() != null) { |
|---|
| | 260 | for (FileStatus file : cs.getChangedFiles()) { |
|---|
| | 261 | IPath hgRoot = new Path(cs.getHgRoot() |
|---|
| | 262 | .getCanonicalPath()); |
|---|
| | 263 | IPath fileRelPath = new Path(file.getPath()); |
|---|
| | 264 | IPath fileAbsPath = hgRoot.append(fileRelPath); |
|---|
| | 265 | |
|---|
| | 266 | SortedSet<ChangeSet> revs = addChangeSetRevisions(cs, |
|---|
| | 267 | fileAbsPath); |
|---|
| | 268 | fileRevisions.put(fileAbsPath, revs); |
|---|
| | 269 | |
|---|
| | 270 | } |
|---|
| | 271 | } |
|---|
| | 272 | |
|---|
| | 273 | // hg root |
|---|
| | 274 | IPath repoPath = new Path(cs.getHgRoot().getCanonicalPath()); |
|---|
| | 275 | |
|---|
| | 276 | SortedSet<ChangeSet> projectRevs = addChangeSetRevisions(cs, |
|---|
| | 277 | repoPath); |
|---|
| | 278 | |
|---|
| | 279 | fileRevisions.put(repoPath, projectRevs); |
|---|
| | 280 | |
|---|
| | 281 | // given path |
|---|
| | 282 | IPath path = res.getLocation(); |
|---|
| | 283 | SortedSet<ChangeSet> pathRevs = addChangeSetRevisions(cs, path); |
|---|
| | 284 | fileRevisions.put(path, pathRevs); |
|---|
| | 285 | |
|---|
| | 286 | } catch (IOException e) { |
|---|
| | 287 | MercurialEclipsePlugin.logError(e); |
|---|
| | 288 | throw new HgException(e.getLocalizedMessage(), e); |
|---|
| | 289 | } |
|---|
| | 290 | } |
|---|
| | 291 | |
|---|
| | 292 | /** |
|---|
| | 293 | * @param fileRevisions |
|---|
| | 294 | * @param cs |
|---|
| | 295 | * @param res |
|---|
| | 296 | * @return |
|---|
| | 297 | */ |
|---|
| | 298 | private static SortedSet<ChangeSet> addChangeSetRevisions(ChangeSet cs, |
|---|
| | 299 | IPath path) { |
|---|
| | 300 | SortedSet<ChangeSet> fileRevs = fileRevisions.get(path); |
|---|
| | 301 | if (fileRevs == null) { |
|---|
| | 302 | fileRevs = new TreeSet<ChangeSet>(); |
|---|
| | 303 | } |
|---|
| | 304 | fileRevs.add(cs); |
|---|
| | 305 | return fileRevs; |
|---|
| | 306 | } |
|---|
| | 307 | |
|---|
| | 308 | } |
|---|
| | 309 | |
|---|
| 187 | | /* |
|---|
| 188 | | * Would be nice to do this as a single XML document using the SAX |
|---|
| 189 | | * parser but I haven't worked out how to get a mercurial style file to |
|---|
| 190 | | * create a valid XML document (cannot get the closing element output) |
|---|
| 191 | | */ |
|---|
| 192 | | String[] changeSetStrings = input.split("\n====+\n"); |
|---|
| 193 | | File hgRoot = HgRootClient.getHgRoot(res.getLocation().toFile()); |
|---|
| 194 | | for (String changeSet : changeSetStrings) { |
|---|
| 195 | | ChangeSet cs; |
|---|
| 196 | | cs = getChangeSet(repository, |
|---|
| 197 | | bundleFile, |
|---|
| 198 | | direction, |
|---|
| 199 | | hgRoot, |
|---|
| 200 | | changeSet); |
|---|
| 201 | | |
|---|
| 202 | | // add bundle file for being able to look into the bundle. |
|---|
| 203 | | // cs.setRepository(repository); |
|---|
| 204 | | // cs.setBundleFile(bundleFile); |
|---|
| 205 | | // cs.setDirection(direction); |
|---|
| 206 | | // cs.setHgRoot(hgRoot); |
|---|
| 207 | | |
|---|
| 208 | | // changeset to resources & project |
|---|
| 209 | | addChangesetToResourceMap(res.getLocation(), fileRevisions, cs); |
|---|
| | 434 | File hgRoot = MercurialTeamProvider.getHgRoot(res); |
|---|
| | 435 | String myInput = "<top>".concat(input).concat("</top>"); |
|---|
| | 436 | try { |
|---|
| | 437 | XMLReader reader = XMLReaderFactory.createXMLReader(); |
|---|
| | 438 | reader.setContentHandler(getHandler(res, direction, repository, |
|---|
| | 439 | bundleFile, hgRoot, fileRevisions)); |
|---|
| | 440 | reader.parse(new InputSource(new ByteArrayInputStream(myInput |
|---|
| | 441 | .getBytes()))); |
|---|
| | 442 | } catch (Exception e) { |
|---|
| | 443 | String nextTry = cleanControlChars(myInput); |
|---|
| | 444 | try { |
|---|
| | 445 | XMLReader reader = XMLReaderFactory.createXMLReader(); |
|---|
| | 446 | reader.setContentHandler(getHandler(res, direction, repository, |
|---|
| | 447 | bundleFile, hgRoot, fileRevisions)); |
|---|
| | 448 | reader.parse(new InputSource(new ByteArrayInputStream(nextTry |
|---|
| | 449 | .getBytes()))); |
|---|
| | 450 | } catch (Exception e1) { |
|---|
| | 451 | throw new HgException(e1.getLocalizedMessage(), e); |
|---|
| | 452 | } |
|---|
| 274 | | private static FileStatus[] getFileStatuses(Element csn) { |
|---|
| 275 | | HashSet<String> files = getFilesValue(csn, "fl"); |
|---|
| 276 | | HashSet<String> adds = getFilesValue(csn, "fa"); |
|---|
| 277 | | HashSet<String> del = getFilesValue(csn, "fd"); |
|---|
| 278 | | |
|---|
| 279 | | files.removeAll(adds); |
|---|
| 280 | | files.removeAll(del); |
|---|
| 281 | | |
|---|
| 282 | | List<FileStatus> statuses = new ArrayList<FileStatus>(files.size()); |
|---|
| 283 | | addFiles(statuses, files, Action.MODIFIED); |
|---|
| 284 | | addFiles(statuses, adds, Action.ADDED); |
|---|
| 285 | | addFiles(statuses, del, Action.REMOVED); |
|---|
| 286 | | |
|---|
| 287 | | return statuses.toArray(new FileStatus[statuses.size()]); |
|---|
| 288 | | } |
|---|
| 289 | | |
|---|
| 290 | | private static HashSet<String> getFilesValue(Element csn, String name) { |
|---|
| 291 | | NodeList nl = csn.getElementsByTagName(name); |
|---|
| 292 | | if (nl.getLength() == 0) { |
|---|
| 293 | | return new HashSet<String>(0); |
|---|
| 294 | | } |
|---|
| 295 | | NodeList files = ((Element) nl.item(0)).getElementsByTagName("f"); |
|---|
| 296 | | HashSet<String> ret = new HashSet<String>(files.getLength()); |
|---|
| 297 | | for (int i = 0; i < files.getLength(); i++) { |
|---|
| 298 | | ret.add(files.item(i).getTextContent()); |
|---|
| 299 | | } |
|---|
| 300 | | |
|---|
| 301 | | return ret; |
|---|
| 302 | | } |
|---|
| 303 | | |
|---|
| 304 | | private static void addFiles(List<FileStatus> statuses, |
|---|
| 305 | | HashSet<String> files, Action action) { |
|---|
| 306 | | for (String f : files) { |
|---|
| 307 | | statuses.add(new FileStatus(action, f)); |
|---|
| 308 | | } |
|---|
| 309 | | } |
|---|
| 310 | | |
|---|
| 311 | | private static String[] splitClean(String string, String sep) { |
|---|
| 312 | | if (string == null || string.length() == 0) { |
|---|
| 313 | | return new String[] {}; |
|---|
| 314 | | } |
|---|
| 315 | | return string.split(sep); |
|---|
| 316 | | } |
|---|
| 317 | | |
|---|
| 318 | | private static String unescape(String string) { |
|---|
| 319 | | return string.replaceAll("<", "<").replaceAll(">", ">") |
|---|
| 320 | | .replaceAll("&", "&"); |
|---|
| 321 | | } |
|---|
| 322 | | |
|---|
| 356 | | |
|---|
| 357 | | /** |
|---|
| 358 | | * Parse a changeset as output from the log command (see |
|---|
| 359 | | * {@link #createMercurialRevisions()}). |
|---|
| 360 | | * @param hgRoot |
|---|
| 361 | | * @param direction |
|---|
| 362 | | * @param bundleFile |
|---|
| 363 | | * @param repository |
|---|
| 364 | | * |
|---|
| 365 | | * @param changeSet |
|---|
| 366 | | * @return |
|---|
| 367 | | */ |
|---|
| 368 | | private static ChangeSet getChangeSet(HgRepositoryLocation repository, |
|---|
| 369 | | File bundleFile, |
|---|
| 370 | | Direction direction, |
|---|
| 371 | | File hgRoot, |
|---|
| 372 | | String changeSet) throws HgException { |
|---|
| 373 | | |
|---|
| 374 | | if (changeSet == null) { |
|---|
| 375 | | return null; |
|---|
| 376 | | } |
|---|
| 377 | | |
|---|
| 378 | | String outputString = changeSet.substring(changeSet.indexOf("<cs>")); |
|---|
| 379 | | |
|---|
| 380 | | try { |
|---|
| 381 | | DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory |
|---|
| 382 | | .newInstance(); |
|---|
| 383 | | DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); |
|---|
| 384 | | Document doc; |
|---|
| 385 | | Reader ir = null; |
|---|
| 386 | | try { |
|---|
| 387 | | ir = new StringReader(outputString); |
|---|
| 388 | | doc = docBuilder.parse(new InputSource(ir)); |
|---|
| 389 | | } catch (SAXException e) { |
|---|
| 390 | | outputString = cleanControlChars(outputString); |
|---|
| 391 | | ir = new StringReader(outputString); |
|---|
| 392 | | doc = docBuilder.parse(new InputSource(ir)); |
|---|
| 393 | | } finally { |
|---|
| 394 | | if (ir != null) { |
|---|
| 395 | | ir.close(); |
|---|
| 396 | | } |
|---|
| 397 | | } |
|---|
| 398 | | |
|---|
| 399 | | // normalize text representation |
|---|
| 400 | | doc.getDocumentElement().normalize(); |
|---|
| 401 | | |
|---|
| 402 | | NodeList csnl = doc.getElementsByTagName("cs"); |
|---|
| 403 | | int totalCs = csnl.getLength(); |
|---|
| 404 | | if (totalCs != 1) { |
|---|
| 405 | | // Something screwy going on, should have 1 and 1 only. |
|---|
| 406 | | throw new HgException( |
|---|
| 407 | | "Cannot parse changeset, bad log output?: " + changeSet); |
|---|
| 408 | | } |
|---|
| 409 | | Element csn = (Element) csnl.item(0); |
|---|
| 410 | | |
|---|
| 411 | | ChangeSet.Builder csb = new ChangeSet.Builder(Integer.parseInt(getValue(csn, "rv")), |
|---|
| 412 | | getValue(csn, "nl"), |
|---|
| 413 | | getValue(csn, "br"), |
|---|
| 414 | | getValue(csn, "di"), |
|---|
| 415 | | getValue(csn, |
|---|
| 416 | | "au")); |
|---|
| 417 | | csb.tag(getValue(csn, "tg")); |
|---|
| 418 | | csb.nodeShort(getValue(csn, "ns")); |
|---|
| 419 | | csb.ageDate(getValue(csn, "da")); |
|---|
| 420 | | csb.description(untab(unescape(getValue(csn, "de")))); |
|---|
| 421 | | csb.parents(splitClean(getValue(csn, "pr"), " ")); |
|---|
| 422 | | csb.changedFiles(getFileStatuses(csn)); |
|---|
| 423 | | |
|---|
| 424 | | csb.hgRoot(hgRoot).bundleFile(bundleFile).repository(repository).direction(direction); |
|---|
| 425 | | return csb.build(); |
|---|
| 426 | | } catch (ParserConfigurationException e) { |
|---|
| 427 | | throw new HgException("Changeset parser Configuration error", e); |
|---|
| 428 | | } catch (SAXException e) { |
|---|
| 429 | | throw new HgException("Changeset parsing error for \"" + changeSet |
|---|
| 430 | | + "\"", e); |
|---|
| 431 | | } catch (IOException e) { |
|---|
| 432 | | throw new HgException("Error parsing changeset \"" + changeSet |
|---|
| 433 | | + "\"", e); |
|---|
| 434 | | } |
|---|
| 435 | | } |
|---|
| 436 | | |
|---|
| 437 | | /** |
|---|
| 438 | | * @param csn |
|---|
| 439 | | * @return |
|---|
| 440 | | */ |
|---|
| 441 | | private static String getValue(Element csn, String name) { |
|---|
| 442 | | return csn.getElementsByTagName(name).item(0).getTextContent(); |
|---|
| 443 | | } |
|---|
| 444 | | |
|---|