1 37 package net.sourceforge.cruisecontrol.sourcecontrols; 38 39 import java.io.BufferedReader ; 40 import java.io.File ; 41 import java.io.IOException ; 42 import java.io.InputStream ; 43 import java.io.InputStreamReader ; 44 import java.io.PrintWriter ; 45 import java.text.ParseException ; 46 import java.text.SimpleDateFormat ; 47 import java.util.ArrayList ; 48 import java.util.Date ; 49 import java.util.Hashtable ; 50 import java.util.List ; 51 import java.util.Map ; 52 import java.util.StringTokenizer ; 53 54 import net.sourceforge.cruisecontrol.CruiseControlException; 55 import net.sourceforge.cruisecontrol.Modification; 56 import net.sourceforge.cruisecontrol.SourceControl; 57 import net.sourceforge.cruisecontrol.util.Commandline; 58 import net.sourceforge.cruisecontrol.util.DateUtil; 59 import net.sourceforge.cruisecontrol.util.OSEnvironment; 60 import net.sourceforge.cruisecontrol.util.StreamConsumer; 61 import net.sourceforge.cruisecontrol.util.StreamPumper; 62 import net.sourceforge.cruisecontrol.util.ValidationHelper; 63 64 import org.apache.log4j.Logger; 65 66 87 public class ConcurrentVersionsSystem implements SourceControl { 88 91 static final String OFFICIAL_CVS_NAME = "CVS"; 92 static final Version DEFAULT_CVS_SERVER_VERSION = new Version(OFFICIAL_CVS_NAME, "1.11"); 93 public static final String LOG_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss z"; 94 95 98 static class Version { 99 private final String cvsName; 100 private final String cvsVersion; 101 102 public Version(String name, String version) { 103 if (name == null) { 104 throw new IllegalArgumentException ("name can't be null"); 105 } 106 if (version == null) { 107 throw new IllegalArgumentException ("version can't be null"); 108 } 109 this.cvsName = name; 110 this.cvsVersion = version; 111 } 112 113 public String getCvsName() { 114 return cvsName; 115 } 116 117 public String getCvsVersion() { 118 return cvsVersion; 119 } 120 121 public boolean equals(Object o) { 122 if (this == o) { 123 return true; 124 } else if (!(o instanceof Version)) { 125 return false; 126 } 127 128 final Version version = (Version) o; 129 130 if (!cvsName.equals(version.cvsName)) { 131 return false; 132 } else if (!cvsVersion.equals(version.cvsVersion)) { 133 return false; 134 } 135 136 return true; 137 } 138 139 public int hashCode() { 140 int result; 141 result = cvsName.hashCode(); 142 result = 29 * result + cvsVersion.hashCode(); 143 return result; 144 } 145 146 public String toString() { 147 return cvsName + " " + cvsVersion; 148 } 149 } 150 151 152 private Hashtable properties = new Hashtable (); 153 private String property; 154 private String propertyOnDelete; 155 156 161 private Hashtable mailAliases; 162 163 167 private String cvsroot; 168 169 173 private String local; 174 175 178 private String tag; 179 180 183 private String module; 184 185 188 private Version cvsServerVersion; 189 190 193 private static Logger log = Logger.getLogger(ConcurrentVersionsSystem.class); 194 195 198 private static final String CVS_FILE_DELIM = 199 "============================================================================="; 200 201 205 private static final String CVS_RCSFILE_LINE = "RCS file: "; 206 207 211 private static final String CVS_WORKINGFILE_LINE = "Working file: "; 212 213 217 private static final String CVS_REVISION_DELIM = "----------------------------"; 218 219 223 private static final String CVS_REVISION_DATE = "date:"; 224 225 229 private static final String CVS_HEAD_TAG = "HEAD"; 230 231 235 private static final String CVS_DESCRIPTION = "description:"; 236 237 242 private static final String CVS_REVISION_DEAD = "dead"; 243 244 247 private static final String NEW_LINE = System.getProperty("line.separator"); 248 249 252 private final SimpleDateFormat logDateFormatter = new SimpleDateFormat (LOG_DATE_FORMAT); 253 254 259 public void setCvsRoot(String cvsroot) { 260 this.cvsroot = cvsroot; 261 } 262 263 269 public void setLocalWorkingCopy(String local) { 270 this.local = local; 271 } 272 273 279 public void setTag(String tag) { 280 this.tag = tag; 281 } 282 283 289 public void setModule(String module) { 290 this.module = module; 291 } 292 293 public void setProperty(String property) { 294 this.property = property; 295 } 296 297 public void setPropertyOnDelete(String propertyOnDelete) { 298 this.propertyOnDelete = propertyOnDelete; 299 } 300 301 protected Version getCvsServerVersion() { 302 if (cvsServerVersion == null) { 303 304 Commandline commandLine = getCommandline(); 305 commandLine.setExecutable("cvs"); 306 307 if (cvsroot != null) { 308 commandLine.createArgument().setValue("-d"); 309 commandLine.createArgument().setValue(cvsroot); 310 } 311 312 commandLine.createArgument().setLine("version"); 313 314 Process p = null; 315 try { 316 if (local != null) { 317 commandLine.setWorkingDirectory(local); 318 } 319 320 p = commandLine.execute(); 321 logErrorStream(p); 322 InputStream is = p.getInputStream(); 323 BufferedReader in = new BufferedReader (new InputStreamReader (is)); 324 325 cvsServerVersion = extractCVSServerVersionFromCVSVersionCommandOutput(in); 326 327 log.debug("cvs server version: " + cvsServerVersion); 328 329 p.waitFor(); 330 p.getInputStream().close(); 331 p.getOutputStream().close(); 332 p.getErrorStream().close(); 333 } catch (IOException e) { 334 log.error("Failed reading cvs server version", e); 335 } catch (CruiseControlException e) { 336 log.error("Failed reading cvs server version", e); 337 } catch (InterruptedException e) { 338 log.error("Failed reading cvs server version", e); 339 } 340 341 if (p == null || p.exitValue() != 0 || cvsServerVersion == null) { 342 if (p == null) { 343 log.debug("Process p was null in CVS.getCvsServerVersion()"); 344 } else { 345 log.debug("Process exit value = " + p.exitValue()); 346 } 347 cvsServerVersion = DEFAULT_CVS_SERVER_VERSION; 348 log.warn("problem getting cvs server version; using " + cvsServerVersion); 349 } 350 } 351 return cvsServerVersion; 352 } 353 354 365 private Version extractCVSServerVersionFromCVSVersionCommandOutput(BufferedReader in) throws IOException { 366 String line = in.readLine(); 367 if (line == null) { 368 return null; 369 } 370 if (line.startsWith("Client:")) { 371 line = in.readLine(); 372 if (line == null) { 373 return null; 374 } 375 if (!line.startsWith("Server:")) { 376 log.warn("Warning expected a line starting with \"Server:\" but got " + line); 377 } 379 } 380 log.debug("server version line: " + line); 381 int nameBegin = line.indexOf(" ("); 382 int nameEnd = line.indexOf(") ", nameBegin); 383 final String name; 384 final String version; 385 if (nameBegin == -1 || nameEnd < nameBegin || nameBegin + 2 >= line.length()) { 386 log.warn("cvs server version name couldn't be parsed from " + line); 387 return null; 388 } 389 name = line.substring(nameBegin + 2, nameEnd); 390 int verEnd = line.indexOf(" ", nameEnd + 2); 391 if (verEnd < nameEnd + 2) { 392 log.warn("cvs server version number couldn't be parsed from " + line); 393 return null; 394 } 395 version = line.substring(nameEnd + 2, verEnd); 396 397 return new Version(name, version); 398 } 399 400 public boolean isCvsNewOutputFormat() { 401 Version version = getCvsServerVersion(); 402 if (OFFICIAL_CVS_NAME.equals(version.getCvsName())) { 403 String csv = version.getCvsVersion(); 404 StringTokenizer st = new StringTokenizer (csv, "."); 405 try { 406 st.nextToken(); 407 int subversion = Integer.parseInt(st.nextToken()); 408 if (subversion > 11) { 409 if (subversion == 12) { 410 if (Integer.parseInt(st.nextToken()) < 9) { 411 return false; 412 } 413 } 414 return true; 415 } 416 } catch (Throwable e) { 417 log.warn("problem identifying cvs server. Assuming output is of 'old' type"); 418 } 419 } 420 return false; 421 } 422 423 424 public Map getProperties() { 425 return properties; 426 } 427 428 431 protected OSEnvironment getOSEnvironment() { 432 return new OSEnvironment(); 433 } 434 435 public void validate() throws CruiseControlException { 436 ValidationHelper.assertFalse(local == null && (cvsroot == null || module == null), 437 "must specify either 'localWorkingCopy' or 'cvsroot' and 'module' on CVS"); 438 ValidationHelper.assertFalse(local != null && (cvsroot != null || module != null), 439 "if 'localWorkingCopy' is specified then cvsroot and module are not allowed on CVS"); 440 ValidationHelper.assertFalse(local != null && !new File (local).exists(), 441 "Local working copy \"" + local + "\" does not exist!"); 442 } 443 444 451 public List getModifications(Date lastBuild, Date now) { 452 mailAliases = getMailAliases(); 453 454 List mods = null; 455 try { 456 mods = execHistoryCommand(buildHistoryCommand(lastBuild, now)); 457 } catch (Exception e) { 458 log.error("Log command failed to execute succesfully", e); 459 } 460 461 if (mods == null) { 462 return new ArrayList (); 463 } 464 return mods; 465 } 466 467 474 private Hashtable getMailAliases() { 475 if (mailAliases == null) { 476 mailAliases = new Hashtable (); 477 Commandline commandLine = getCommandline(); 478 commandLine.setExecutable("cvs"); 479 480 if (cvsroot != null) { 481 commandLine.createArgument().setValue("-d"); 482 commandLine.createArgument().setValue(cvsroot); 483 } 484 485 commandLine.createArgument().setLine("-q co -p CVSROOT/users"); 486 487 Process p = null; 488 try { 489 if (local != null) { 490 commandLine.setWorkingDirectory(local); 491 } 492 493 p = commandLine.execute(); 494 logErrorStream(p); 495 InputStream is = p.getInputStream(); 496 BufferedReader in = new BufferedReader (new InputStreamReader (is)); 497 498 String line; 499 while ((line = in.readLine()) != null) { 500 addAliasToMap(line); 501 } 502 503 p.waitFor(); 504 p.getInputStream().close(); 505 p.getOutputStream().close(); 506 p.getErrorStream().close(); 507 } catch (Exception e) { 508 log.error("Failed reading mail aliases", e); 509 } 510 511 if (p == null || p.exitValue() != 0) { 512 if (p == null) { 513 log.debug("Process p was null in CVS.getMailAliases()"); 514 } else { 515 log.debug("Process exit value = " + p.exitValue()); 516 } 517 log.warn("problem getting CVSROOT/users; using empty email map"); 518 mailAliases = new Hashtable (); 519 } 520 } 521 522 return mailAliases; 523 } 524 525 void addAliasToMap(String line) { 526 log.debug("Mapping " + line); 527 int colon = line.indexOf(':'); 528 529 if (colon >= 0) { 530 String user = line.substring(0, colon); 531 String address = line.substring(colon + 1); 532 mailAliases.put(user, address); 533 534 } 535 } 536 537 542 public Commandline buildHistoryCommand(Date lastBuildTime, Date checkTime) throws CruiseControlException { 543 Commandline commandLine = getCommandline(); 544 commandLine.setExecutable("cvs"); 545 546 if (cvsroot != null) { 547 commandLine.createArgument().setValue("-d"); 548 commandLine.createArgument().setValue(cvsroot); 549 } 550 commandLine.createArgument().setValue("-q"); 551 552 if (local != null) { 553 commandLine.setWorkingDirectory(local); 554 commandLine.createArgument().setValue("log"); 555 } else { 556 commandLine.createArgument().setValue("rlog"); 557 } 558 559 boolean useHead = tag == null || tag.equals(CVS_HEAD_TAG) || tag.equals(""); 560 if (useHead) { 561 commandLine.createArgument().setValue("-N"); 562 } 563 String dateRange = formatCVSDate(lastBuildTime) + "<" + formatCVSDate(checkTime); 564 commandLine.createArgument().setValue("-d" + dateRange); 565 566 if (!useHead) { 567 570 commandLine.createArgument().setValue("-r" + tag); 572 } else { 573 commandLine.createArgument().setValue("-b"); 575 } 576 577 if (local == null) { 578 commandLine.createArgument().setValue(module); 579 } 580 581 return commandLine; 582 } 583 584 protected Commandline getCommandline() { 586 return new Commandline(); 587 } 588 589 static String formatCVSDate(Date date) { 590 return DateUtil.formatCVSDate(date); 591 } 592 593 602 protected List parseStream(InputStream input) throws IOException { 603 BufferedReader reader = new BufferedReader (new InputStreamReader (input)); 604 605 609 String line = readToNotPast(reader, CVS_RCSFILE_LINE, null); 610 ArrayList mods = new ArrayList (); 611 612 while (line != null) { 613 List returnList = parseEntry(reader, line); 616 617 mods.addAll(returnList); 619 620 line = readToNotPast(reader, CVS_RCSFILE_LINE, null); 623 } 624 625 return mods; 626 } 627 628 private void getRidOfLeftoverData(InputStream stream) { 629 StreamPumper outPumper = new StreamPumper(stream, (PrintWriter ) null); 630 new Thread (outPumper).start(); 631 } 632 633 private List execHistoryCommand(Commandline command) throws Exception { 634 Process p = command.execute(); 635 636 logErrorStream(p); 637 InputStream cvsLogStream = p.getInputStream(); 638 List mods = parseStream(cvsLogStream); 639 640 getRidOfLeftoverData(cvsLogStream); 641 p.waitFor(); 642 p.getInputStream().close(); 643 p.getOutputStream().close(); 644 p.getErrorStream().close(); 645 646 return mods; 647 } 648 649 protected void setMailAliases(Hashtable mailAliases) { 650 this.mailAliases = mailAliases; 651 } 652 653 private static void logErrorStream(Process p) { 654 logErrorStream(p.getErrorStream()); 655 } 656 static void logErrorStream(InputStream error) { 657 StreamConsumer warnLogger = new StreamConsumer() { 658 public void consumeLine(String line) { 659 log.warn(line); 660 } 661 }; 662 StreamPumper errorPumper = 663 new StreamPumper(error, null, warnLogger); 664 new Thread (errorPumper).start(); 665 } 666 667 669 680 private List parseEntry(BufferedReader reader, String rcsLine) throws IOException { 681 ArrayList mods = new ArrayList (); 682 683 String nextLine = ""; 684 685 String workingFileName; 688 if (module != null && cvsroot != null) { 689 final String repositoryRoot = cvsroot.substring(cvsroot.lastIndexOf(":") + 1); 690 final int startAt = "RCS file: ".length() + repositoryRoot.length(); 691 workingFileName = rcsLine.substring(startAt, rcsLine.length() - 2); 692 } else { 693 String workingFileLine = readToNotPast(reader, CVS_WORKINGFILE_LINE, null); 694 workingFileName = workingFileLine.substring(CVS_WORKINGFILE_LINE.length()); 695 } 696 697 String branchRevisionName = parseBranchRevisionName(reader, tag); 698 boolean newCVSVersion = isCvsNewOutputFormat(); 699 while (nextLine != null && !nextLine.startsWith(CVS_FILE_DELIM)) { 700 nextLine = readToNotPast(reader, "revision", CVS_FILE_DELIM); 701 if (nextLine == null) { 702 break; 704 } 705 706 StringTokenizer tokens = new StringTokenizer (nextLine, " "); 707 tokens.nextToken(); 708 String revision = tokens.nextToken(); 709 if (tag != null && !tag.equals(CVS_HEAD_TAG)) { 710 if (!revision.equals(branchRevisionName)) { 711 String itsBranchRevisionName = revision.substring(0, revision.lastIndexOf('.')); 713 if (!itsBranchRevisionName.equals(branchRevisionName)) { 714 break; 715 } 716 } 717 } 718 719 nextLine = readToNotPast(reader, CVS_REVISION_DATE, CVS_FILE_DELIM); 722 if (nextLine == null) { 723 break; 724 } 725 726 tokens = new StringTokenizer (nextLine, " \t\n\r\f;"); 727 tokens.nextToken(); 730 String dateStamp = tokens.nextToken(); 731 String timeStamp = tokens.nextToken(); 732 733 if (newCVSVersion) { 735 tokens.nextToken(); 736 } 737 tokens.nextToken(); 739 String authorName = tokens.nextToken(); 740 741 tokens.nextToken(); 743 String stateKeyword = tokens.nextToken(); 744 745 boolean isAdded = !tokens.hasMoreTokens(); 747 748 String message = ""; 751 nextLine = reader.readLine(); 752 boolean multiLine = false; 753 754 while (nextLine != null 755 && !nextLine.startsWith(CVS_FILE_DELIM) 756 && !nextLine.startsWith(CVS_REVISION_DELIM)) { 757 758 if (multiLine) { 759 message += NEW_LINE; 760 } else { 761 multiLine = true; 762 } 763 message += nextLine; 764 765 nextLine = reader.readLine(); 767 } 768 769 Modification nextModification = new Modification("cvs"); 770 nextModification.revision = revision; 771 772 int lastSlashIndex = workingFileName.lastIndexOf("/"); 773 774 String fileName, folderName = null; 775 fileName = workingFileName.substring(lastSlashIndex + 1); 776 if (lastSlashIndex != -1) { 777 folderName = workingFileName.substring(0, lastSlashIndex); 778 } 779 Modification.ModifiedFile modfile = nextModification.createModifiedFile(fileName, folderName); 780 modfile.revision = nextModification.revision; 781 782 try { 783 if (newCVSVersion) { 784 nextModification.modifiedTime = DateUtil.parseCVSDate( 785 dateStamp + " " + timeStamp + " GMT"); 786 } else { 787 nextModification.modifiedTime = logDateFormatter.parse(dateStamp + " " + timeStamp + " GMT"); 788 } 789 } catch (ParseException pe) { 790 log.error("Error parsing cvs log for date and time", pe); 791 return null; 792 } 793 794 nextModification.userName = authorName; 795 796 String address = (String ) mailAliases.get(authorName); 797 if (address != null) { 798 nextModification.emailAddress = address; 799 } 800 801 nextModification.comment = (message != null ? message : ""); 802 803 if (stateKeyword.equalsIgnoreCase(CVS_REVISION_DEAD) 804 && message.indexOf("was initially added on branch") != -1) { 805 log.debug("skipping branch addition activity for " + nextModification); 806 continue; 808 } 809 810 if (stateKeyword.equalsIgnoreCase(CVS_REVISION_DEAD)) { 811 modfile.action = "deleted"; 812 if (propertyOnDelete != null) { 813 properties.put(propertyOnDelete, "true"); 814 } 815 } else if (isAdded) { 816 modfile.action = "added"; 817 } else { 818 modfile.action = "modified"; 819 } 820 if (property != null) { 821 properties.put(property, "true"); 822 } 823 mods.add(nextModification); 824 } 825 return mods; 826 } 827 828 837 private static String parseBranchRevisionName(BufferedReader reader, final String tag) throws IOException { 838 String branchRevisionName = null; 839 840 if (tag != null && !tag.equals(CVS_HEAD_TAG)) { 841 844 String branchRevisionLine = readToNotPast(reader, "\t" + tag + ": ", CVS_DESCRIPTION); 845 846 if (branchRevisionLine != null) { 847 branchRevisionName = branchRevisionLine.substring(tag.length() + 3); 849 if (branchRevisionName.charAt(branchRevisionName.lastIndexOf(".") - 1) == '0') { 850 branchRevisionName = 851 branchRevisionName.substring(0, branchRevisionName.lastIndexOf(".") - 2) 852 + branchRevisionName.substring(branchRevisionName.lastIndexOf(".")); 853 } 854 } 855 } 856 return branchRevisionName; 857 } 858 859 874 private static String readToNotPast(BufferedReader reader, String beginsWith, String notPast) 875 throws IOException { 876 boolean checkingNotPast = notPast != null; 877 878 String nextLine = reader.readLine(); 879 while (nextLine != null && !nextLine.startsWith(beginsWith)) { 880 if (checkingNotPast && nextLine.startsWith(notPast)) { 881 return null; 882 } 883 nextLine = reader.readLine(); 884 } 885 886 return nextLine; 887 } 888 889 } 890 | Popular Tags |