1 37 package net.sourceforge.cruisecontrol.sourcecontrols; 38 39 import net.sourceforge.cruisecontrol.CruiseControlException; 40 import net.sourceforge.cruisecontrol.Modification; 41 import net.sourceforge.cruisecontrol.SourceControl; 42 import net.sourceforge.cruisecontrol.util.Commandline; 43 import net.sourceforge.cruisecontrol.util.StreamPumper; 44 import net.sourceforge.cruisecontrol.util.ValidationHelper; 45 46 import org.apache.log4j.Logger; 47 import org.jdom.Document; 48 import org.jdom.Element; 49 import org.jdom.JDOMException; 50 import org.jdom.input.SAXBuilder; 51 52 import java.io.File ; 53 import java.io.IOException ; 54 import java.io.InputStream ; 55 import java.io.InputStreamReader ; 56 import java.io.PrintWriter ; 57 import java.io.Reader ; 58 import java.io.UnsupportedEncodingException ; 59 import java.text.ParseException ; 60 import java.text.SimpleDateFormat ; 61 import java.text.DateFormat ; 62 import java.util.ArrayList ; 63 import java.util.Arrays ; 64 import java.util.Date ; 65 import java.util.Hashtable ; 66 import java.util.Iterator ; 67 import java.util.List ; 68 import java.util.Map ; 69 import java.util.TimeZone ; 70 71 83 public class SVN implements SourceControl { 84 85 private static final Logger LOG = Logger.getLogger(SVN.class); 86 87 88 private static final String SVN_DATE_FORMAT_IN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; 89 90 91 private static final String SVN_DATE_FORMAT_OUT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; 92 93 private Hashtable properties = new Hashtable (); 94 private String property; 95 private String propertyOnDelete; 96 97 98 private String repositoryLocation; 99 private String localWorkingCopy; 100 private String userName; 101 private String password; 102 103 public Map getProperties() { 104 return properties; 105 } 106 107 public void setProperty(String property) { 108 this.property = property; 109 } 110 111 public void setPropertyOnDelete(String propertyOnDelete) { 112 this.propertyOnDelete = propertyOnDelete; 113 } 114 115 121 public void setRepositoryLocation(String repositoryLocation) { 122 this.repositoryLocation = repositoryLocation; 123 } 124 125 132 public void setLocalWorkingCopy(String localWorkingCopy) { 133 this.localWorkingCopy = localWorkingCopy; 134 } 135 136 139 public void setUsername(String userName) { 140 this.userName = userName; 141 } 142 143 146 public void setPassword(String password) { 147 this.password = password; 148 } 149 150 158 public void validate() throws CruiseControlException { 159 ValidationHelper.assertTrue(repositoryLocation != null || localWorkingCopy != null, 160 "At least 'repositoryLocation'or 'localWorkingCopy' is a required attribute on the Subversion task "); 161 162 if (localWorkingCopy != null) { 163 File workingDir = new File (localWorkingCopy); 164 ValidationHelper.assertTrue(workingDir.exists() && workingDir.isDirectory(), 165 "'localWorkingCopy' must be an existing directory. Was " 166 + workingDir.getAbsolutePath()); 167 } 168 } 169 170 176 public List getModifications(Date lastBuild, Date now) { 177 List modifications = new ArrayList (); 178 Commandline command; 179 try { 180 command = buildHistoryCommand(lastBuild, now); 181 } catch (CruiseControlException e) { 182 LOG.error("Error building history command", e); 183 return modifications; 184 } 185 try { 186 modifications = execHistoryCommand(command, lastBuild); 187 } catch (Exception e) { 188 LOG.error("Error executing svn log command " + command, e); 189 } 190 fillPropertiesIfNeeded(modifications); 191 return modifications; 192 } 193 194 195 202 Commandline buildHistoryCommand(Date lastBuild, Date checkTime) throws CruiseControlException { 203 Commandline command = new Commandline(); 204 command.setExecutable("svn"); 205 206 if (localWorkingCopy != null) { 207 command.setWorkingDirectory(localWorkingCopy); 208 } 209 210 command.createArgument().setValue("log"); 211 command.createArgument().setValue("--non-interactive"); 212 command.createArgument().setValue("--xml"); 213 command.createArgument().setValue("-v"); 214 command.createArgument().setValue("-r"); 215 command.createArgument().setValue("{" + formatSVNDate(lastBuild) + "}" + ":" 216 + "{" + formatSVNDate(checkTime) + "}"); 217 218 if (userName != null) { 219 command.createArgument().setValue("--username"); 220 command.createArgument().setValue(userName); 221 } 222 if (password != null) { 223 command.createArgument().setValue("--password"); 224 command.createArgument().setValue(password); 225 } 226 if (repositoryLocation != null) { 227 command.createArgument().setValue(repositoryLocation); 228 } 229 230 LOG.debug("Executing command: " + command); 231 232 return command; 233 } 234 235 236 static String formatSVNDate(Date lastBuild) { 237 DateFormat f = new SimpleDateFormat (SVN_DATE_FORMAT_IN); 238 f.setTimeZone(TimeZone.getTimeZone("GMT")); 239 return f.format(lastBuild); 240 } 241 242 243 private List execHistoryCommand(Commandline command, Date lastBuild) 244 throws InterruptedException , IOException , ParseException , JDOMException { 245 246 Process p = command.execute(); 247 248 logErrorStream(p); 249 InputStream svnStream = p.getInputStream(); 250 List modifications = parseStream(svnStream, lastBuild); 251 252 p.waitFor(); 253 p.getInputStream().close(); 254 p.getOutputStream().close(); 255 p.getErrorStream().close(); 256 257 return modifications; 258 } 259 260 private void logErrorStream(Process p) { 261 StreamPumper errorPumper = 262 new StreamPumper(p.getErrorStream(), new PrintWriter (System.err, true)); 263 new Thread (errorPumper).start(); 264 } 265 266 private List parseStream(InputStream svnStream, Date lastBuild) 267 throws JDOMException, IOException , ParseException , UnsupportedEncodingException { 268 269 InputStreamReader reader = new InputStreamReader (svnStream, "UTF-8"); 270 return SVNLogXMLParser.parseAndFilter(reader, lastBuild); 271 } 272 273 void fillPropertiesIfNeeded(List modifications) { 274 if (property != null) { 275 if (modifications.size() > 0) { 276 properties.put(property, "true"); 277 } 278 } 279 280 if (propertyOnDelete != null) { 281 for (int i = 0; i < modifications.size(); i++) { 282 Modification modification = (Modification) modifications.get(i); 283 Modification.ModifiedFile file = (Modification.ModifiedFile) modification.files.get(0); 284 if (file.action.equals("deleted")) { 285 properties.put(propertyOnDelete, "true"); 286 break; 287 } 288 } 289 } 290 } 291 292 public static DateFormat getOutDateFormatter() { 293 DateFormat f = new SimpleDateFormat (SVN_DATE_FORMAT_OUT); 294 f.setTimeZone(TimeZone.getTimeZone("GMT")); 295 return f; 296 } 297 298 static final class SVNLogXMLParser { 299 300 private SVNLogXMLParser() { 301 } 302 303 304 static List parseAndFilter(Reader reader, Date lastBuild) 305 throws ParseException , JDOMException, IOException { 306 307 Modification[] modifications = parse(reader); 308 return filterModifications(modifications, lastBuild); 309 } 310 311 312 static Modification[] parse(Reader reader) 313 throws ParseException , JDOMException, IOException { 314 315 SAXBuilder builder = new SAXBuilder(false); 316 Document document = builder.build(reader); 317 return parseDOMTree(document); 318 } 319 320 static Modification[] parseDOMTree(Document document) throws ParseException { 321 List modifications = new ArrayList (); 322 323 Element rootElement = document.getRootElement(); 324 List logEntries = rootElement.getChildren("logentry"); 325 for (Iterator iterator = logEntries.iterator(); iterator.hasNext();) { 326 Element logEntry = (Element) iterator.next(); 327 328 Modification[] modificationsOfRevision = parseLogEntry(logEntry); 329 modifications.addAll(Arrays.asList(modificationsOfRevision)); 330 } 331 332 return (Modification[]) modifications.toArray(new Modification[modifications.size()]); 333 } 334 335 static Modification[] parseLogEntry(Element logEntry) throws ParseException { 336 List modifications = new ArrayList (); 337 338 Element logEntryPaths = logEntry.getChild("paths"); 339 if (logEntryPaths != null) { 340 List paths = logEntryPaths.getChildren("path"); 341 for (Iterator iterator = paths.iterator(); iterator.hasNext();) { 342 Element path = (Element) iterator.next(); 343 344 Modification modification = new Modification("svn"); 345 346 modification.modifiedTime = convertDate(logEntry.getChildText("date")); 347 modification.userName = logEntry.getChildText("author"); 348 modification.comment = logEntry.getChildText("msg"); 349 modification.revision = logEntry.getAttributeValue("revision"); 350 351 Modification.ModifiedFile modfile = modification.createModifiedFile(path.getText(), null); 352 modfile.action = convertAction(path.getAttributeValue("action")); 353 modfile.revision = modification.revision; 354 355 modifications.add(modification); 356 } 357 } 358 359 return (Modification[]) modifications.toArray(new Modification[modifications.size()]); 360 } 361 362 368 static Date convertDate(String date) throws ParseException { 369 final int zIndex = date.indexOf('Z'); 370 if (zIndex - 3 < 0) { 371 throw new ParseException (date 372 + " doesn't match the expected subversion date format", date.length()); 373 } 374 String withoutMicroSeconds = date.substring(0, zIndex - 3); 375 376 return getOutDateFormatter().parse(withoutMicroSeconds); 377 } 378 379 static String convertAction(String action) { 380 if (action.equals("A")) { 381 return "added"; 382 } 383 if (action.equals("M")) { 384 return "modified"; 385 } 386 if (action.equals("D")) { 387 return "deleted"; 388 } 389 return "unknown"; 390 } 391 392 403 static List filterModifications(Modification[] modifications, Date lastBuild) { 404 List filtered = new ArrayList (); 405 for (int i = 0; i < modifications.length; i++) { 406 Modification modification = modifications[i]; 407 if (modification.modifiedTime.getTime() > lastBuild.getTime()) { 408 filtered.add(modification); 409 } 410 } 411 return filtered; 412 } 413 } 414 } 415 | Popular Tags |