1 package hudson.scm; 2 3 import hudson.FilePath; 4 import hudson.FilePath.FileCallable; 5 import hudson.Launcher; 6 import hudson.Proc; 7 import hudson.Util; 8 import static hudson.Util.fixEmpty; 9 import hudson.model.AbstractBuild; 10 import hudson.model.AbstractModelObject; 11 import hudson.model.AbstractProject; 12 import hudson.model.Action; 13 import hudson.model.BuildListener; 14 import hudson.model.Descriptor; 15 import hudson.model.Hudson; 16 import hudson.model.Job; 17 import hudson.model.LargeText; 18 import hudson.model.ModelObject; 19 import hudson.model.Run; 20 import hudson.model.TaskListener; 21 import hudson.org.apache.tools.ant.taskdefs.cvslib.ChangeLogTask; 22 import hudson.remoting.RemoteOutputStream; 23 import hudson.remoting.VirtualChannel; 24 import hudson.util.ArgumentListBuilder; 25 import hudson.util.ByteBuffer; 26 import hudson.util.ForkOutputStream; 27 import hudson.util.FormFieldValidator; 28 import hudson.util.StreamTaskListener; 29 import org.apache.tools.ant.BuildException; 30 import org.apache.tools.ant.taskdefs.Expand; 31 import org.apache.tools.zip.ZipEntry; 32 import org.apache.tools.zip.ZipOutputStream; 33 import org.kohsuke.stapler.StaplerRequest; 34 import org.kohsuke.stapler.StaplerResponse; 35 36 import javax.servlet.ServletException ; 37 import javax.servlet.http.HttpServletResponse ; 38 import java.io.BufferedOutputStream ; 39 import java.io.BufferedReader ; 40 import java.io.ByteArrayInputStream ; 41 import java.io.ByteArrayOutputStream ; 42 import java.io.File ; 43 import java.io.FileInputStream ; 44 import java.io.FileOutputStream ; 45 import java.io.FileReader ; 46 import java.io.IOException ; 47 import java.io.InputStreamReader ; 48 import java.io.OutputStream ; 49 import java.io.PrintWriter ; 50 import java.io.Reader ; 51 import java.io.Serializable ; 52 import java.io.StringWriter ; 53 import java.lang.ref.WeakReference ; 54 import java.text.DateFormat ; 55 import java.util.ArrayList ; 56 import java.util.Collections ; 57 import java.util.Date ; 58 import java.util.Enumeration ; 59 import java.util.HashMap ; 60 import java.util.HashSet ; 61 import java.util.List ; 62 import java.util.Locale ; 63 import java.util.Map ; 64 import java.util.Map.Entry; 65 import java.util.Set ; 66 import java.util.StringTokenizer ; 67 import java.util.TimeZone ; 68 import java.util.TreeSet ; 69 import java.util.regex.Matcher ; 70 import java.util.regex.Pattern ; 71 72 85 public class CVSSCM extends AbstractCVSFamilySCM implements Serializable { 86 89 private String cvsroot; 90 91 97 private String module; 98 99 private String branch; 100 101 private String cvsRsh; 102 103 private boolean canUseUpdate; 104 105 109 private boolean flatten; 110 111 112 public CVSSCM(String cvsroot, String module,String branch,String cvsRsh,boolean canUseUpdate, boolean flatten) { 113 this.cvsroot = cvsroot; 114 this.module = module.trim(); 115 this.branch = nullify(branch); 116 this.cvsRsh = nullify(cvsRsh); 117 this.canUseUpdate = canUseUpdate; 118 this.flatten = flatten && module.indexOf(' ')==-1; 119 } 120 121 private String compression() { 122 boolean local = cvsroot.startsWith("/") || cvsroot.startsWith(":local:") || cvsroot.startsWith(":fork:"); 127 return local ? "-z0" : "-z3"; 130 } 131 132 public String getCvsRoot() { 133 return cvsroot; 134 } 135 136 140 public FilePath getModuleRoot(FilePath workspace) { 141 if(flatten) 142 return workspace; 143 144 int idx = module.indexOf(' '); 145 if(idx>=0) return workspace.child(module.substring(0,idx)); 146 else return workspace.child(module); 147 } 148 149 public ChangeLogParser createChangeLogParser() { 150 return new CVSChangeLogParser(); 151 } 152 153 public String getAllModules() { 154 return module; 155 } 156 157 160 public String getBranch() { 161 return branch; 162 } 163 164 public String getCvsRsh() { 165 return cvsRsh; 166 } 167 168 public boolean getCanUseUpdate() { 169 return canUseUpdate; 170 } 171 172 public boolean isFlatten() { 173 return flatten; 174 } 175 176 public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath dir, TaskListener listener) throws IOException , InterruptedException { 177 List <String > changedFiles = update(true, launcher, dir, listener, new Date ()); 178 179 return changedFiles!=null && !changedFiles.isEmpty(); 180 } 181 182 private void configureDate(ArgumentListBuilder cmd, Date date) { DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.US); 184 df.setTimeZone(TimeZone.getTimeZone("UTC")); cmd.add("-D", df.format(date)); 186 } 187 188 public boolean checkout(AbstractBuild build, Launcher launcher, FilePath dir, BuildListener listener, File changelogFile) throws IOException , InterruptedException { 189 List <String > changedFiles = null; 191 if(canUseUpdate && isUpdatable(dir)) { 192 changedFiles = update(false, launcher, dir, listener, build.getTimestamp().getTime()); 193 if(changedFiles==null) 194 return false; } else { 196 if(!checkout(launcher,dir,listener,build.getTimestamp().getTime())) 197 return false; 198 } 199 200 File archiveFile = getArchiveFile(build); 202 final OutputStream os = new RemoteOutputStream(new FileOutputStream (archiveFile)); 203 204 build.getProject().getWorkspace().act(new FileCallable<Void >() { 205 public Void invoke(File ws, VirtualChannel channel) throws IOException { 206 ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream (os)); 207 208 if(flatten) { 209 archive(ws, module, zos,true); 210 } else { 211 StringTokenizer tokens = new StringTokenizer (module); 212 while(tokens.hasMoreTokens()) { 213 String m = tokens.nextToken(); 214 File mf = new File (ws, m); 215 216 if(!mf.exists()) 217 continue; 220 221 if(!mf.isDirectory()) { 222 int idx = m.lastIndexOf('/'); 225 if(idx==-1) 226 throw new Error ("Kohsuke probe: m="+m); 227 m = m.substring(0, idx); 228 mf = mf.getParentFile(); 229 } 230 archive(mf,m,zos,true); 231 } 232 } 233 zos.close(); 234 return null; 235 } 236 }); 237 238 build.getActions().add(new TagAction(build)); 240 241 return calcChangeLog(build, changedFiles, changelogFile, listener); 242 } 243 244 public boolean checkout(Launcher launcher, FilePath dir, TaskListener listener) throws IOException , InterruptedException { 245 Date now = new Date (); 246 if(canUseUpdate && isUpdatable(dir)) { 247 return update(false, launcher, dir, listener, now)!=null; 248 } else { 249 return checkout(launcher,dir,listener, now); 250 } 251 } 252 253 private boolean checkout(Launcher launcher, FilePath dir, TaskListener listener, Date dt) throws IOException , InterruptedException { 254 dir.deleteContents(); 255 256 ArgumentListBuilder cmd = new ArgumentListBuilder(); 257 cmd.add(getDescriptor().getCvsExe(),debugLogging?"-t":"-Q",compression(),"-d",cvsroot,"co"); 258 if(branch!=null) 259 cmd.add("-r",branch); 260 if(flatten) 261 cmd.add("-d",dir.getName()); 262 configureDate(cmd,dt); 263 cmd.addTokenized(module); 264 265 return run(launcher,cmd,listener, flatten ? dir.getParent() : dir); 266 } 267 268 271 private static File getArchiveFile(AbstractBuild build) { 272 return new File (build.getRootDir(),"workspace.zip"); 273 } 274 275 281 private void archive(File dir,String relPath,ZipOutputStream zos, boolean isRoot) throws IOException { 282 Set <String > knownFiles = new HashSet <String >(); 283 parseCVSEntries(new File (dir,"CVS/Entries"),knownFiles); 285 parseCVSEntries(new File (dir,"CVS/Entries.Log"),knownFiles); 286 parseCVSEntries(new File (dir,"CVS/Entries.Extra"),knownFiles); 287 boolean hasCVSdirs = !knownFiles.isEmpty(); 288 knownFiles.add("CVS"); 289 290 File [] files = dir.listFiles(); 291 if(files==null) { 292 if(isRoot) 293 throw new IOException ("No such directory exists. Did you specify the correct branch/tag?: "+dir); 294 else 295 throw new IOException ("No such directory exists. Looks like someone is modifying the workspace concurrently: "+dir); 296 } 297 for( File f : files ) { 298 String name = relPath+'/'+f.getName(); 299 if(f.isDirectory()) { 300 if(hasCVSdirs && !knownFiles.contains(f.getName())) { 301 continue; 304 } 305 archive(f,name,zos,false); 306 } else { 307 if(!dir.getName().equals("CVS")) 308 continue; 310 zos.putNextEntry(new ZipEntry(name)); 311 FileInputStream fis = new FileInputStream (f); 312 Util.copyStream(fis,zos); 313 fis.close(); 314 zos.closeEntry(); 315 } 316 } 317 } 318 319 322 private void parseCVSEntries(File entries, Set <String > knownFiles) throws IOException { 323 if(!entries.exists()) 324 return; 325 326 BufferedReader in = new BufferedReader (new InputStreamReader (new FileInputStream (entries))); 327 String line; 328 while((line=in.readLine())!=null) { 329 String [] tokens = line.split("/+"); 330 if(tokens==null || tokens.length<2) continue; knownFiles.add(tokens[1]); 332 } 333 in.close(); 334 } 335 336 343 private List <String > update(boolean dryRun, Launcher launcher, FilePath workspace, TaskListener listener, Date date) throws IOException , InterruptedException { 344 345 List <String > changedFileNames = new ArrayList <String >(); 347 ArgumentListBuilder cmd = new ArgumentListBuilder(); 348 cmd.add(getDescriptor().getCvsExe(),"-q",compression()); 349 if(dryRun) 350 cmd.add("-n"); 351 cmd.add("update","-PdC"); 352 if (branch != null) { 353 cmd.add("-r", branch); 354 } 355 configureDate(cmd, date); 356 357 if(flatten) { 358 ByteArrayOutputStream baos = new ByteArrayOutputStream (); 359 360 if(!run(launcher,cmd,listener,workspace, 361 new ForkOutputStream(baos,listener.getLogger()))) 362 return null; 363 364 parseUpdateOutput("",baos, changedFileNames); 365 } else { 366 @SuppressWarnings ("unchecked") final Set <String > moduleNames = new TreeSet (Collections.list(new StringTokenizer (module))); 368 369 moduleNames.addAll(workspace.act(new FileCallable<Set <String >>() { 371 public Set <String > invoke(File ws, VirtualChannel channel) throws IOException { 372 File [] subdirs = ws.listFiles(); 373 if (subdirs != null) { 374 SUBDIR: for (File s : subdirs) { 375 if (new File (s, "CVS").isDirectory()) { 376 String top = s.getName(); 377 for (String mod : moduleNames) { 378 if (mod.startsWith(top + "/")) { 379 continue SUBDIR; 383 } 384 } 385 moduleNames.add(top); 386 } 387 } 388 } 389 return moduleNames; 390 } 391 })); 392 393 for (String moduleName : moduleNames) { 394 ByteArrayOutputStream baos = new ByteArrayOutputStream (); 396 FilePath modulePath = new FilePath(workspace, moduleName); 397 398 ArgumentListBuilder actualCmd = cmd; 399 String baseName = moduleName; 400 401 if(!modulePath.isDirectory()) { 402 actualCmd = cmd.clone(); 405 actualCmd.add(modulePath.getName()); 406 modulePath = modulePath.getParent(); 407 int slash = baseName.lastIndexOf('/'); 408 if (slash > 0) { 409 baseName = baseName.substring(0, slash); 410 } 411 } 412 413 if(!run(launcher,actualCmd,listener, 414 modulePath, 415 new ForkOutputStream(baos,listener.getLogger()))) 416 return null; 417 418 parseUpdateOutput(baseName+'/',baos, changedFileNames); 421 } 422 } 423 424 return changedFileNames; 425 } 426 427 private static final Pattern UPDATE_LINE = Pattern.compile("[UPARMC] (.+)"); 430 431 private static final Pattern REMOVAL_LINE = Pattern.compile("cvs (server|update): `?(.+?)'? is no longer in the repository"); 432 434 442 private void parseUpdateOutput(String baseName, ByteArrayOutputStream output, List <String > result) throws IOException { 443 BufferedReader in = new BufferedReader (new InputStreamReader ( 444 new ByteArrayInputStream (output.toByteArray()))); 445 String line; 446 while((line=in.readLine())!=null) { 447 Matcher matcher = UPDATE_LINE.matcher(line); 448 if(matcher.matches()) { 449 result.add(baseName+matcher.group(1)); 450 continue; 451 } 452 453 matcher= REMOVAL_LINE.matcher(line); 454 if(matcher.matches()) { 455 result.add(baseName+matcher.group(2)); 456 continue; 457 } 458 459 } 467 } 468 469 472 private boolean isUpdatable(FilePath dir) throws IOException , InterruptedException { 473 return dir.act(new FileCallable<Boolean >() { 474 public Boolean invoke(File dir, VirtualChannel channel) throws IOException { 475 if(flatten) { 476 return isUpdatableModule(dir); 477 } else { 478 StringTokenizer tokens = new StringTokenizer (module); 479 while(tokens.hasMoreTokens()) { 480 File module = new File (dir,tokens.nextToken()); 481 if(!isUpdatableModule(module)) 482 return false; 483 } 484 return true; 485 } 486 } 487 }); 488 } 489 490 private boolean isUpdatableModule(File module) { 491 if(!module.isDirectory()) 492 module = module.getParentFile(); 494 495 File cvs = new File (module,"CVS"); 496 if(!cvs.exists()) 497 return false; 498 499 if(!checkContents(new File (cvs,"Root"),cvsroot)) 501 return false; 502 if(branch!=null) { 503 if(!checkContents(new File (cvs,"Tag"),'T'+branch)) 504 return false; 505 } else { 506 File tag = new File (cvs,"Tag"); 507 if (tag.exists()) { 508 try { 509 Reader r = new FileReader (tag); 510 try { 511 String s = new BufferedReader (r).readLine(); 512 return s != null && s.startsWith("D"); 513 } finally { 514 r.close(); 515 } 516 } catch (IOException e) { 517 return false; 518 } 519 } 520 } 521 522 return true; 523 } 524 525 530 private boolean checkContents(File file, String contents) { 531 try { 532 Reader r = new FileReader (file); 533 try { 534 String s = new BufferedReader (r).readLine(); 535 if (s == null) return false; 536 return s.trim().equals(contents.trim()); 537 } finally { 538 r.close(); 539 } 540 } catch (IOException e) { 541 return false; 542 } 543 } 544 545 546 549 class ChangeLogResult implements Serializable { 550 boolean hadError; 551 String errorOutput; 552 553 public ChangeLogResult(boolean hadError, String errorOutput) { 554 this.hadError = hadError; 555 if(hadError) 556 this.errorOutput = errorOutput; 557 } 558 private static final long serialVersionUID = 1L; 559 } 560 561 564 class BuildExceptionWithLog extends RuntimeException { 565 final String errorOutput; 566 567 public BuildExceptionWithLog(BuildException cause, String errorOutput) { 568 super(cause); 569 this.errorOutput = errorOutput; 570 } 571 572 private static final long serialVersionUID = 1L; 573 } 574 575 588 private boolean calcChangeLog(AbstractBuild build, final List <String > changedFiles, File changelogFile, final BuildListener listener) throws InterruptedException { 589 if(build.getPreviousBuild()==null || (changedFiles!=null && changedFiles.isEmpty())) { 590 listener.getLogger().println("$ no changes detected"); 593 return createEmptyChangeLog(changelogFile,listener, "changelog"); 594 } 595 596 listener.getLogger().println("$ computing changelog"); 597 598 FilePath baseDir = build.getProject().getWorkspace(); 599 final String cvspassFile = getDescriptor().getCvspassFile(); 600 final String cvsExe = getDescriptor().getCvsExe(); 601 602 try { 603 final Date startTime = build.getPreviousBuild().getTimestamp().getTime(); 605 final Date endTime = build.getTimestamp().getTime(); 606 final OutputStream out = new RemoteOutputStream(new FileOutputStream (changelogFile)); 607 608 ChangeLogResult result = baseDir.act(new FileCallable<ChangeLogResult>() { 609 public ChangeLogResult invoke(File ws, VirtualChannel channel) throws IOException { 610 final StringWriter errorOutput = new StringWriter (); 611 final boolean[] hadError = new boolean[1]; 612 613 ChangeLogTask task = new ChangeLogTask() { 614 public void log(String msg, int msgLevel) { 615 if(msgLevel==org.apache.tools.ant.Project.MSG_ERR) { 618 hadError[0] = true; 619 errorOutput.write(msg); 620 errorOutput.write('\n'); 621 return; 622 } 623 if(debugLogging) { 624 listener.getLogger().println(msg); 625 } 626 } 627 }; 628 task.setProject(new org.apache.tools.ant.Project()); 629 task.setCvsExe(cvsExe); 630 task.setDir(ws); 631 if(cvspassFile.length()!=0) 632 task.setPassfile(new File (cvspassFile)); 633 task.setCvsRoot(cvsroot); 634 task.setCvsRsh(cvsRsh); 635 task.setFailOnError(true); 636 task.setDeststream(new BufferedOutputStream (out)); 637 task.setBranch(branch); 638 task.setStart(startTime); 639 task.setEnd(endTime); 640 if(changedFiles!=null) { 641 for (String filePath : changedFiles) { 644 if(new File (ws,filePath).getParentFile().exists()) 645 task.addFile(filePath); 646 } 647 } else { 648 if(!flatten) 650 task.setPackage(module); 651 } 652 653 try { 654 task.execute(); 655 } catch (BuildException e) { 656 throw new BuildExceptionWithLog(e,errorOutput.toString()); 657 } 658 659 return new ChangeLogResult(hadError[0],errorOutput.toString()); 660 } 661 }); 662 663 if(result.hadError) { 664 listener.getLogger().print(result.errorOutput); 666 } 667 return true; 668 } catch( BuildExceptionWithLog e ) { 669 listener.getLogger().print(e.errorOutput); 671 BuildException x = (BuildException) e.getCause(); 673 PrintWriter w = listener.error(x.getMessage()); 674 w.println("Working directory is "+baseDir); 675 x.printStackTrace(w); 676 return false; 677 } catch( RuntimeException e ) { 678 e.printStackTrace(listener.error(e.getMessage())); 681 return true; } catch( IOException e ) { 683 e.printStackTrace(listener.error("Failed to detect changlog")); 684 return true; 685 } 686 } 687 688 public DescriptorImpl getDescriptor() { 689 return DescriptorImpl.DESCRIPTOR; 690 } 691 692 public void buildEnvVars(Map <String ,String > env) { 693 if(cvsRsh!=null) 694 env.put("CVS_RSH",cvsRsh); 695 String cvspass = getDescriptor().getCvspassFile(); 696 if(cvspass.length()!=0) 697 env.put("CVS_PASSFILE",cvspass); 698 } 699 700 public static final class DescriptorImpl extends Descriptor<SCM> implements ModelObject { 701 static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 702 703 706 private String cvsPassFile; 707 708 711 private String cvsExe; 712 713 716 private volatile Map <String ,RepositoryBrowser> browsers = new HashMap <String ,RepositoryBrowser>(); 717 718 class RepositoryBrowser { 719 String diffURL; 720 String browseURL; 721 } 722 723 DescriptorImpl() { 724 super(CVSSCM.class); 725 load(); 726 } 727 728 protected void convert(Map <String , Object > oldPropertyBag) { 729 cvsPassFile = (String )oldPropertyBag.get("cvspass"); 730 } 731 732 public String getDisplayName() { 733 return "CVS"; 734 } 735 736 public SCM newInstance(StaplerRequest req) { 737 return new CVSSCM( 738 req.getParameter("cvs_root"), 739 req.getParameter("cvs_module"), 740 req.getParameter("cvs_branch"), 741 req.getParameter("cvs_rsh"), 742 req.getParameter("cvs_use_update")!=null, 743 req.getParameter("cvs_legacy")==null 744 ); 745 } 746 747 public String getCvspassFile() { 748 String value = cvsPassFile; 749 if(value==null) 750 value = ""; 751 return value; 752 } 753 754 public String getCvsExe() { 755 if(cvsExe==null) return "cvs"; 756 else return cvsExe; 757 } 758 759 public void setCvspassFile(String value) { 760 cvsPassFile = value; 761 save(); 762 } 763 764 767 public String getDiffURL(String cvsRoot, String pathName, String oldRev, String newRev) { 768 RepositoryBrowser b = browsers.get(cvsRoot); 769 if(b==null) return null; 770 return b.diffURL.replaceAll("%%P",pathName).replace("%%r",oldRev).replace("%%R",newRev); 771 772 } 773 774 public boolean configure( StaplerRequest req ) { 775 cvsPassFile = fixEmpty(req.getParameter("cvs_cvspass").trim()); 776 cvsExe = fixEmpty(req.getParameter("cvs_exe").trim()); 777 778 Map <String ,RepositoryBrowser> browsers = new HashMap <String , RepositoryBrowser>(); 779 int i=0; 780 while(true) { 781 String root = req.getParameter("cvs_repobrowser_cvsroot" + i); 782 if(root==null) break; 783 784 RepositoryBrowser rb = new RepositoryBrowser(); 785 rb.browseURL = req.getParameter("cvs_repobrowser"+i); 786 rb.diffURL = req.getParameter("cvs_repobrowser_diff"+i); 787 browsers.put(root,rb); 788 789 i++; 790 } 791 this.browsers = browsers; 792 793 save(); 794 795 return true; 796 } 797 798 802 public void doCvsPassCheck(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 803 new FormFieldValidator(req,rsp,true) { 806 protected void check() throws IOException , ServletException { 807 String v = fixEmpty(request.getParameter("value")); 808 if(v==null) { 809 ok(); 811 } else { 812 File cvsPassFile = new File (v); 813 814 if(cvsPassFile.exists()) { 815 ok(); 816 } else { 817 error("No such file exists"); 818 } 819 } 820 } 821 }.process(); 822 } 823 824 827 public void doCvsExeCheck(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 828 new FormFieldValidator(req,rsp,true) { 831 protected void check() throws IOException , ServletException { 832 String cvsExe = fixEmpty(request.getParameter("value")); 833 if(cvsExe==null) { 834 ok(); 835 return; 836 } 837 838 if(cvsExe.indexOf(File.separatorChar)>=0) { 839 if(new File (cvsExe).exists()) { 841 ok(); 842 } else { 843 error("There's no such file: "+cvsExe); 844 } 845 } else { 846 ok(); 848 } 849 850 } 851 }.process(); 852 } 853 854 857 public void doVersion(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 858 ByteBuffer baos = new ByteBuffer(); 859 try { 860 Proc proc = Hudson.getInstance().createLauncher(TaskListener.NULL).launch( 861 new String []{getCvsExe(), "--version"}, new String [0], baos, null); 862 proc.join(); 863 rsp.setContentType("text/plain"); 864 baos.writeTo(rsp.getOutputStream()); 865 } catch (IOException e) { 866 req.setAttribute("error",e); 867 rsp.forward(this,"versionCheckError",req); 868 } 869 } 870 871 876 public void doCvsrootCheck(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 877 new FormFieldValidator(req,rsp,false) { 878 protected void check() throws IOException , ServletException { 879 String v = fixEmpty(request.getParameter("value")); 880 if(v==null) { 881 error("CVSROOT is mandatory"); 882 return; 883 } 884 885 Matcher m = CVSROOT_PSERVER_PATTERN.matcher(v); 886 887 if(v.startsWith(":pserver") || v.startsWith(":ext")) { 889 if(!m.matches()) { 890 error("Invalid CVSROOT string"); 891 return; 892 } 893 } 897 898 if(v.startsWith(":pserver")) { 901 if(m.group(2)==null) { String cvspass = getCvspassFile(); 903 File passfile; 904 if(cvspass.equals("")) { 905 passfile = new File (new File (System.getProperty("user.home")),".cvspass"); 906 } else { 907 passfile = new File (cvspass); 908 } 909 910 if(passfile.exists()) { 911 if(!scanCvsPassFile(passfile, v)) { 917 error("It doesn't look like this CVSROOT has its password set." + 918 " Would you like to set it now?"); 919 return; 920 } 921 } 922 } 923 } 924 925 ok(); 927 } 928 }.process(); 929 } 930 931 934 private boolean scanCvsPassFile(File passfile, String cvsroot) throws IOException { 935 cvsroot += ' '; 936 String cvsroot2 = "/1 "+cvsroot; BufferedReader in = new BufferedReader (new FileReader (passfile)); 938 try { 939 String line; 940 while((line=in.readLine())!=null) { 941 int portIndex = line.indexOf(":2401/"); 944 String line2 = ""; 945 if(portIndex>=0) 946 line2 = line.substring(0,portIndex+1)+line.substring(portIndex+5); 948 if(line.startsWith(cvsroot) || line.startsWith(cvsroot2) || line2.startsWith(cvsroot2)) 949 return true; 950 } 951 return false; 952 } finally { 953 in.close(); 954 } 955 } 956 957 private static final Pattern CVSROOT_PSERVER_PATTERN = 958 Pattern.compile(":(ext|pserver):[^@:]+(:[^@:]+)?@[^:]+:(\\d+:)?.+"); 959 960 966 public void doPostPassword(StaplerRequest req, StaplerResponse rsp) throws IOException { 967 if(!Hudson.adminCheck(req,rsp)) 968 return; 969 970 String cvsroot = req.getParameter("cvsroot"); 971 String password = req.getParameter("password"); 972 973 if(cvsroot==null || password==null) { 974 rsp.setStatus(HttpServletResponse.SC_BAD_REQUEST); 975 return; 976 } 977 978 rsp.setContentType("text/plain"); 979 Proc proc = Hudson.getInstance().createLauncher(TaskListener.NULL).launch( 980 new String []{getCvsExe(), "-d",cvsroot,"login"}, new String [0], 981 new ByteArrayInputStream ((password+"\n").getBytes()), 982 rsp.getOutputStream()); 983 proc.join(); 984 } 985 } 986 987 990 public final class TagAction extends AbstractModelObject implements Action { 991 private final AbstractBuild build; 992 993 996 private volatile String tagName; 997 998 1002 private transient volatile TagWorkerThread workerThread; 1003 1004 1007 private transient WeakReference <LargeText> log; 1008 1009 public TagAction(AbstractBuild build) { 1010 this.build = build; 1011 } 1012 1013 public String getIconFileName() { 1014 return "save.gif"; 1015 } 1016 1017 public String getDisplayName() { 1018 return "Tag this build"; 1019 } 1020 1021 public String getUrlName() { 1022 return "tagBuild"; 1023 } 1024 1025 public String getTagName() { 1026 return tagName; 1027 } 1028 1029 public TagWorkerThread getWorkerThread() { 1030 return workerThread; 1031 } 1032 1033 public AbstractBuild getBuild() { 1034 return build; 1035 } 1036 1037 public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 1038 req.setAttribute("build",build); 1039 req.getView(this,chooseAction()).forward(req,rsp); 1040 } 1041 1042 private synchronized String chooseAction() { 1043 if(tagName!=null) 1044 return "alreadyTagged.jelly"; 1045 if(workerThread!=null) 1046 return "inProgress.jelly"; 1047 return "tagForm.jelly"; 1048 } 1049 1050 1053 public synchronized void doSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 1054 Map <AbstractBuild,String > tagSet = new HashMap <AbstractBuild,String >(); 1055 1056 String name = req.getParameter("name"); 1057 if(isInvalidTag(name)) { 1058 sendError("No valid tag name given",req,rsp); 1059 return; 1060 } 1061 1062 tagSet.put(build,name); 1063 1064 if(req.getParameter("upstream")!=null) { 1065 Enumeration e = req.getParameterNames(); 1067 Map <AbstractProject, Integer > upstreams = build.getUpstreamBuilds(); 1069 while(e.hasMoreElements()) { 1070 String upName = (String ) e.nextElement(); 1071 if(!upName.startsWith("upstream.")) 1072 continue; 1073 1074 String tag = req.getParameter(upName); 1075 if(isInvalidTag(tag)) { 1076 sendError("No valid tag name given for "+upName,req,rsp); 1077 return; 1078 } 1079 1080 upName = upName.substring(9); Job p = Hudson.getInstance().getItemByFullName(upName,Job.class); 1082 1083 Run build = p.getBuildByNumber(upstreams.get(p)); 1084 tagSet.put((AbstractBuild) build,tag); 1085 } 1086 } 1087 1088 new TagWorkerThread(tagSet).start(); 1089 1090 doIndex(req,rsp); 1091 } 1092 1093 private boolean isInvalidTag(String name) { 1094 return name==null || name.length()==0; 1095 } 1096 1097 1100 public synchronized void doClearError(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 1101 if(workerThread!=null && !workerThread.isAlive()) 1102 workerThread = null; 1103 doIndex(req,rsp); 1104 } 1105 1106 1109 public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException { 1110 if(log==null) { 1111 rsp.setStatus(HttpServletResponse.SC_OK); 1112 } else { 1113 LargeText text = log.get(); 1114 if(text!=null) 1115 text.doProgressText(req,rsp); 1116 else 1117 rsp.setStatus(HttpServletResponse.SC_OK); 1118 } 1119 } 1120 1121 1124 public void perform(String tagName, TaskListener listener) { 1125 File destdir = null; 1126 try { 1127 destdir = Util.createTempDir(); 1128 1129 listener.getLogger().println("expanding the workspace archive into "+destdir); 1131 Expand e = new Expand(); 1132 e.setProject(new org.apache.tools.ant.Project()); 1133 e.setDest(destdir); 1134 e.setSrc(getArchiveFile(build)); 1135 e.setTaskType("unzip"); 1136 e.execute(); 1137 1138 listener.getLogger().println("tagging the workspace"); 1140 StringTokenizer tokens = new StringTokenizer (CVSSCM.this.module); 1141 while(tokens.hasMoreTokens()) { 1142 String m = tokens.nextToken(); 1143 FilePath path = new FilePath(destdir).child(m); 1144 boolean isDir = path.isDirectory(); 1145 1146 ArgumentListBuilder cmd = new ArgumentListBuilder(); 1147 cmd.add(getDescriptor().getCvsExe(),"tag"); 1148 if(isDir) { 1149 cmd.add("-R"); 1150 } 1151 cmd.add(tagName); 1152 if(!isDir) { 1153 cmd.add(path.getName()); 1154 path = path.getParent(); 1155 } 1156 1157 if(!CVSSCM.this.run(new Launcher.LocalLauncher(listener),cmd,listener, path)) { 1158 listener.getLogger().println("tagging failed"); 1159 return; 1160 } 1161 } 1162 1163 onTagCompleted(tagName); 1165 build.save(); 1166 1167 } catch (Throwable e) { 1168 e.printStackTrace(listener.fatalError(e.getMessage())); 1169 } finally { 1170 try { 1171 if(destdir!=null) { 1172 listener.getLogger().println("cleaning up "+destdir); 1173 Util.deleteRecursive(destdir); 1174 } 1175 } catch (IOException e) { 1176 e.printStackTrace(listener.fatalError(e.getMessage())); 1177 } 1178 } 1179 } 1180 1181 1184 private synchronized void onTagCompleted(String tagName) { 1185 this.tagName = tagName; 1186 this.workerThread = null; 1187 } 1188 } 1189 1190 public static final class TagWorkerThread extends Thread { 1191 private final ByteBuffer log = new ByteBuffer(); 1193 private final LargeText text = new LargeText(log,false); 1194 private final Map <AbstractBuild,String > tagSet; 1195 1196 public TagWorkerThread(Map <AbstractBuild,String > tagSet) { 1197 this.tagSet = tagSet; 1198 } 1199 1200 public String getLog() { 1201 return log.toString(); 1203 } 1204 1205 public synchronized void start() { 1206 for (Entry<AbstractBuild, String > e : tagSet.entrySet()) { 1207 TagAction ta = e.getKey().getAction(TagAction.class); 1208 if(ta!=null) { 1209 ta.workerThread = this; 1210 ta.log = new WeakReference <LargeText>(text); 1211 } 1212 } 1213 1214 super.start(); 1215 } 1216 1217 public void run() { 1218 TaskListener listener = new StreamTaskListener(log); 1219 1220 for (Entry<AbstractBuild, String > e : tagSet.entrySet()) { 1221 TagAction ta = e.getKey().getAction(TagAction.class); 1222 if(ta==null) { 1223 listener.error(e.getKey()+" doesn't have CVS tag associated with it. Skipping"); 1224 continue; 1225 } 1226 listener.getLogger().println("Tagging "+e.getKey()+" to "+e.getValue()); 1227 try { 1228 e.getKey().keepLog(); 1229 } catch (IOException x) { 1230 x.printStackTrace(listener.error("Failed to mark "+e.getKey()+" for keep")); 1231 } 1232 ta.perform(e.getValue(), listener); 1233 listener.getLogger().println(); 1234 } 1235 1236 listener.getLogger().println("Completed"); 1237 text.markAsComplete(); 1238 } 1239 } 1240 1241 1247 public static boolean debugLogging = false; 1248 1249 private static final long serialVersionUID = 1L; 1250} 1251 | Popular Tags |