blob: bc61058443961f507679d90e20e518db162f5659 [file] [log] [blame]
/*
* 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>&lt;!-- Root element --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> changelog <font color=#ff00ff>(entry</font><font color=#ff00ff>+</font><font color=#ff00ff>)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- SVN Entry --&gt;</font>
* <font color=#6a5acd>&lt;!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>&gt;</font>
* <font color=#0000ff>&lt;!-- Date of svn entry --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> date <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- Time of svn entry --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> time <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- Author of change --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> author <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- commit message --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> msg <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- List of paths affected --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> path <font color=#ff00ff>(name,action)</font><font color=#ff00ff></font><font color=#ff00ff>)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- Name of the path --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> name <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> action <font color=#ff00ff>(added|modified|deleted)</font><font color=#6a5acd>&gt;</font>
* <font color=#0000ff>&lt;!-- Revision number --&gt;</font>
* <font color=#6a5acd>&lt;!ELEMENT</font> revision <font color=#ff00ff>(#PCDATA)</font><font color=#6a5acd>&gt;</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;
}
}