| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.ant.svn; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.Properties; |
| import java.util.Vector; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.LogOutputStream; |
| import org.apache.tools.ant.taskdefs.PumpStreamHandler; |
| import org.apache.tools.ant.taskdefs.condition.Os; |
| import org.apache.tools.ant.taskdefs.cvslib.CvsUser; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Examines the output of svn log and group related changes together. |
| * |
| * It produces an XML output representing the list of changes. |
| * <pre> |
| * <font color=#0000ff><!-- Root element --></font> |
| * <font color=#6a5acd><!ELEMENT</font> changelog <font color=#ff00ff>(entry</font><font color=#ff00ff>+</font><font color=#ff00ff>)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- SVN Entry --></font> |
| * <font color=#6a5acd><!ELEMENT</font> entry <font color=#ff00ff>(date,time,revision,author,path</font><font color=#ff00ff>+,msg</font><font color=#ff00ff>,msg)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- Date of svn entry --></font> |
| * <font color=#6a5acd><!ELEMENT</font> date <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- Time of svn entry --></font> |
| * <font color=#6a5acd><!ELEMENT</font> time <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- Author of change --></font> |
| * <font color=#6a5acd><!ELEMENT</font> author <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- commit message --></font> |
| * <font color=#6a5acd><!ELEMENT</font> msg <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- List of paths affected --></font> |
| * <font color=#6a5acd><!ELEMENT</font> path <font color=#ff00ff>(name,action)</font><font color=#ff00ff></font><font color=#ff00ff>)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- Name of the path --></font> |
| * <font color=#6a5acd><!ELEMENT</font> name <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * <font color=#6a5acd><!ELEMENT</font> action <font color=#ff00ff>(added|modified|deleted)</font><font color=#6a5acd>></font> |
| * <font color=#0000ff><!-- Revision number --></font> |
| * <font color=#6a5acd><!ELEMENT</font> revision <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>></font> |
| * </pre> |
| * |
| * @ant.task name="svnchangelog" category="scm" |
| */ |
| public class SvnChangeLogTask extends AbstractSvnTask { |
| // ? On windows ? |
| private static final boolean ON_WINDOWS = Os.isFamily("windows"); |
| |
| /** User list */ |
| private File usersFile; |
| |
| /** User list */ |
| private Vector svnUsers = new Vector(); |
| |
| /** Input dir */ |
| private File inputDir; |
| |
| /** Output file */ |
| private File destFile; |
| |
| /** The earliest revision at which to start processing entries. */ |
| private String startRevision; |
| |
| /** The latest revision at which to stop processing entries. */ |
| private String endRevision; |
| |
| /** |
| * Filesets containing list of files against which the svn log will be |
| * performed. If empty then all files in the working directory will |
| * be checked. |
| */ |
| private final Vector filesets = new Vector(); |
| |
| |
| /** |
| * Set the base dir for svn. |
| * |
| * @param inputDir The new dir value |
| */ |
| public void setDir(final File inputDir) { |
| this.inputDir = inputDir; |
| } |
| |
| |
| /** |
| * Set the output file for the log. |
| * |
| * @param destFile The new destfile value |
| */ |
| public void setDestfile(final File destFile) { |
| this.destFile = destFile; |
| } |
| |
| |
| /** |
| * Set a lookup list of user names & addresses |
| * |
| * @param usersFile The file containing the users info. |
| */ |
| public void setUsersfile(final File usersFile) { |
| this.usersFile = usersFile; |
| } |
| |
| |
| /** |
| * Add a user to list changelog knows about. |
| * |
| * @param user the user |
| */ |
| public void addUser(final CvsUser user) { |
| svnUsers.addElement(user); |
| } |
| |
| |
| /** |
| * Set the revision at which the changelog should start. |
| * |
| * @param start The revision at which the changelog should start. |
| */ |
| public void setStart(final String start) { |
| this.startRevision = start; |
| } |
| |
| |
| /** |
| * Set the revision at which the changelog should stop. |
| * |
| * @param endRevision The revision at which the changelog should stop. |
| */ |
| public void setEnd(final String endRevision) { |
| this.endRevision = endRevision; |
| } |
| |
| |
| /** |
| * Set the number of days worth of log entries to process. |
| * |
| * @param days the number of days of log to process. |
| */ |
| public void setDaysinpast(final int days) { |
| final long time = System.currentTimeMillis() |
| - (long) days * 24 * 60 * 60 * 1000; |
| |
| final SimpleDateFormat outputDate = |
| new SimpleDateFormat("{yyyy-MM-dd}"); |
| String formattedDate = outputDate.format(new Date(time)); |
| if (ON_WINDOWS) { |
| // BugZilla: 40704 |
| // http://issues.apache.org/bugzilla/show_bug.cgi?id=40704 |
| // cygwin svn has a bug in handling parameters with {} |
| // putting quotes around the date fixes this, the |
| // fix also works for the normal svn cli command. |
| formattedDate = "\"" + formattedDate + "\""; |
| } |
| setStart(formattedDate); |
| } |
| |
| |
| /** |
| * Adds a set of files about which svn logs will be generated. |
| * |
| * @param fileSet a set of files about which svn logs will be generated. |
| */ |
| public void addFileset(final FileSet fileSet) { |
| filesets.addElement(fileSet); |
| } |
| |
| |
| /** |
| * Execute task |
| * |
| * @exception BuildException if something goes wrong executing the |
| * svn command |
| */ |
| public void execute() throws BuildException { |
| File savedDir = inputDir; // may be altered in validate |
| |
| try { |
| |
| validate(); |
| final Properties userList = new Properties(); |
| |
| loadUserlist(userList); |
| |
| for (int i = 0, size = svnUsers.size(); i < size; i++) { |
| final CvsUser user = (CvsUser) svnUsers.get(i); |
| user.validate(); |
| userList.put(user.getUserID(), user.getDisplayname()); |
| } |
| |
| setSubCommand("log"); |
| setVerbose(true); |
| |
| if (null != startRevision) { |
| if (null != endRevision) { |
| setRevision(startRevision + ":" + endRevision); |
| } else { |
| setRevision(startRevision + ":HEAD"); |
| } |
| } |
| |
| // Check if list of files to check has been specified |
| if (!filesets.isEmpty()) { |
| final Enumeration e = filesets.elements(); |
| |
| while (e.hasMoreElements()) { |
| final FileSet fileSet = (FileSet) e.nextElement(); |
| final DirectoryScanner scanner = |
| fileSet.getDirectoryScanner(getProject()); |
| final String[] files = scanner.getIncludedFiles(); |
| |
| for (int i = 0; i < files.length; i++) { |
| addSubCommandArgument(files[i]); |
| } |
| } |
| } |
| |
| final SvnChangeLogParser parser = new SvnChangeLogParser(); |
| final PumpStreamHandler handler = |
| new PumpStreamHandler(parser, |
| new LogOutputStream(this, |
| Project.MSG_ERR)); |
| |
| log(getSubCommand(), Project.MSG_VERBOSE); |
| |
| setDest(inputDir); |
| setExecuteStreamHandler(handler); |
| super.execute(); |
| |
| final SvnEntry[] entrySet = parser.getEntrySetAsArray(); |
| final SvnEntry[] filteredEntrySet = filterEntrySet(entrySet); |
| |
| replaceAuthorIdWithName(userList, filteredEntrySet); |
| |
| writeChangeLog(filteredEntrySet); |
| |
| } finally { |
| inputDir = savedDir; |
| } |
| } |
| |
| /** |
| * Validate the parameters specified for task. |
| * |
| * @throws BuildException if fails validation checks |
| */ |
| private void validate() |
| throws BuildException { |
| if (null == inputDir) { |
| inputDir = getDest(); |
| } |
| if (null == destFile) { |
| final String message = "Destfile must be set."; |
| |
| throw new BuildException(message); |
| } |
| if (!inputDir.exists()) { |
| final String message = "Cannot find base dir " |
| + inputDir.getAbsolutePath(); |
| |
| throw new BuildException(message); |
| } |
| if (null != usersFile && !usersFile.exists()) { |
| final String message = "Cannot find user lookup list " |
| + usersFile.getAbsolutePath(); |
| |
| throw new BuildException(message); |
| } |
| } |
| |
| /** |
| * Load the userlist from the userList file (if specified) and add to |
| * list of users. |
| * |
| * @param userList the file of users |
| * @throws BuildException if file can not be loaded for some reason |
| */ |
| private void loadUserlist(final Properties userList) |
| throws BuildException { |
| if (null != usersFile) { |
| try { |
| userList.load(new FileInputStream(usersFile)); |
| } catch (final IOException ioe) { |
| throw new BuildException(ioe.toString(), ioe); |
| } |
| } |
| } |
| |
| /** |
| * Filter the specified entries according to an appropriate rule. |
| * |
| * @param entrySet the entry set to filter |
| * @return the filtered entry set |
| */ |
| private SvnEntry[] filterEntrySet(final SvnEntry[] entrySet) { |
| final Vector results = new Vector(); |
| |
| for (int i = 0; i < entrySet.length; i++) { |
| final SvnEntry svnEntry = entrySet[i]; |
| |
| if (null != endRevision && !isBeforeEndRevision(svnEntry)) { |
| //Skip revisions that are too late |
| continue; |
| } |
| results.addElement(svnEntry); |
| } |
| |
| final SvnEntry[] resultArray = new SvnEntry[results.size()]; |
| |
| results.copyInto(resultArray); |
| return resultArray; |
| } |
| |
| /** |
| * replace all known author's id's with their maven specified names |
| */ |
| private void replaceAuthorIdWithName(final Properties userList, |
| final SvnEntry[] entrySet) { |
| for (int i = 0; i < entrySet.length; i++) { |
| |
| final SvnEntry entry = entrySet[ i ]; |
| if (userList.containsKey(entry.getAuthor())) { |
| entry.setAuthor(userList.getProperty(entry.getAuthor())); |
| } |
| } |
| } |
| |
| /** |
| * Print changelog to file specified in task. |
| * |
| * @param entrySet the entry set to write. |
| * @throws BuildException if there is an error writing changelog. |
| */ |
| private void writeChangeLog(final SvnEntry[] entrySet) |
| throws BuildException { |
| FileOutputStream output = null; |
| |
| try { |
| output = new FileOutputStream(destFile); |
| |
| final PrintWriter writer = |
| new PrintWriter(new OutputStreamWriter(output, "UTF-8")); |
| |
| final SvnChangeLogWriter serializer = new SvnChangeLogWriter(); |
| |
| serializer.printChangeLog(writer, entrySet); |
| } catch (final UnsupportedEncodingException uee) { |
| getProject().log(uee.toString(), Project.MSG_ERR); |
| } catch (final IOException ioe) { |
| throw new BuildException(ioe.toString(), ioe); |
| } finally { |
| FileUtils.close(output); |
| } |
| } |
| |
| private static final String PATTERN = "yyyy-MM-dd"; |
| private static final SimpleDateFormat INPUT_DATE |
| = new SimpleDateFormat(PATTERN); |
| |
| /** |
| * Checks whether a given entry is before the given end revision, |
| * using revision numbers or date information as appropriate. |
| */ |
| private boolean isBeforeEndRevision(SvnEntry entry) { |
| if (endRevision.startsWith("{") |
| && endRevision.length() >= 2 + PATTERN.length() ) { |
| try { |
| Date endDate = |
| INPUT_DATE.parse(endRevision.substring(1, |
| PATTERN.length() |
| + 1)); |
| return entry.getDate().before(endDate); |
| } catch (ParseException e) { |
| } |
| } else { |
| try { |
| int endRev = Integer.parseInt(endRevision); |
| int entryRev = Integer.parseInt(entry.getRevision()); |
| return endRev >= entryRev; |
| } catch (NumberFormatException e) { |
| } // end of try-catch |
| } |
| // failed to parse revision, use a save fallback |
| return true; |
| } |
| } |