| package org.apache.maven.plugin.changelog; |
| |
| /* |
| * 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. |
| */ |
| |
| import org.apache.maven.doxia.sink.Sink; |
| import org.apache.maven.doxia.siterenderer.Renderer; |
| import org.apache.maven.model.Developer; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.reporting.AbstractMavenReport; |
| import org.apache.maven.reporting.MavenReportException; |
| import org.apache.maven.scm.ChangeFile; |
| import org.apache.maven.scm.ChangeSet; |
| import org.apache.maven.scm.ScmBranch; |
| import org.apache.maven.scm.ScmException; |
| import org.apache.maven.scm.ScmFileSet; |
| import org.apache.maven.scm.ScmResult; |
| import org.apache.maven.scm.ScmRevision; |
| import org.apache.maven.scm.command.changelog.ChangeLogScmResult; |
| import org.apache.maven.scm.command.changelog.ChangeLogSet; |
| import org.apache.maven.scm.command.info.InfoItem; |
| import org.apache.maven.scm.command.info.InfoScmResult; |
| import org.apache.maven.scm.log.DefaultLog; |
| import org.apache.maven.scm.manager.ScmManager; |
| import org.apache.maven.scm.provider.ScmProvider; |
| import org.apache.maven.scm.provider.ScmProviderRepository; |
| import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; |
| import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; |
| import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; |
| import org.apache.maven.scm.repository.ScmRepository; |
| import org.apache.maven.settings.Server; |
| import org.apache.maven.settings.Settings; |
| import org.codehaus.plexus.util.ReaderFactory; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.util.WriterFactory; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.net.URLEncoder; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.ResourceBundle; |
| import java.util.StringTokenizer; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Generate a changelog report. |
| * |
| * @version $Id$ |
| */ |
| @Mojo( name = "changelog" ) |
| public class ChangeLogReport |
| extends AbstractMavenReport |
| { |
| /** |
| * A special token that represents the SCM relative path for a file. |
| * It can be used in <code>displayFileDetailUrl</code>. |
| */ |
| private static final String FILE_TOKEN = "%FILE%"; |
| |
| /** |
| * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. |
| * It can be used in the <code>issueLinkUrl</code>. |
| */ |
| private static final String ISSUE_TOKEN = "%ISSUE%"; |
| |
| /** |
| * A special token that represents the SCM revision number. |
| * It can be used in <code>displayChangeSetDetailUrl</code> |
| * and <code>displayFileRevDetailUrl</code>. |
| */ |
| private static final String REV_TOKEN = "%REV%"; |
| |
| /** |
| * The number of days to use as a range, when this is not specified. |
| */ |
| private static final int DEFAULT_RANGE = 30; |
| |
| public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; |
| |
| private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; |
| |
| /** |
| * Used to specify the format to use for the dates in the headings of the |
| * report. |
| * |
| * @since 2.1 |
| */ |
| @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) |
| private String headingDateFormat = "yyyy-MM-dd"; |
| |
| /** |
| * Used to specify whether to build the log using range, tag or date. |
| */ |
| @Parameter( property = "changelog.type", defaultValue = "range", required = true ) |
| private String type; |
| |
| /** |
| * Used to specify the number of days of log entries to retrieve. |
| */ |
| @Parameter( property = "changelog.range", defaultValue = "-1" ) |
| private int range; |
| |
| /** |
| * Used to specify the absolute date (or list of dates) to start log entries from. |
| */ |
| @Parameter |
| private List<String> dates; |
| |
| /** |
| * Used to specify the tag (or list of tags) to start log entries from. |
| */ |
| @Parameter |
| private List<String> tags; |
| |
| /** |
| * Used to specify the date format of the log entries that are retrieved from your SCM system. |
| */ |
| @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) |
| private String dateFormat; |
| |
| /** |
| * Input dir. Directory where the files under SCM control are located. |
| */ |
| @Parameter( property = "basedir", required = true ) |
| private File basedir; |
| |
| /** |
| * Output file for xml document |
| */ |
| @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) |
| private File outputXML; |
| |
| /** |
| * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. |
| */ |
| @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) |
| private int outputXMLExpiration; |
| |
| /** |
| * Comment format string used for interrogating |
| * the revision control system. |
| * Currently only used by the ClearcaseChangeLogGenerator. |
| */ |
| @Parameter( property = "changelog.commentFormat" ) |
| private String commentFormat; |
| |
| /** |
| * The file encoding when writing non-HTML reports. |
| */ |
| @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) |
| private String outputEncoding; |
| |
| /** |
| * The user name (used by svn and starteam protocol). |
| */ |
| @Parameter( property = "username" ) |
| private String username; |
| |
| /** |
| * The user password (used by svn and starteam protocol). |
| */ |
| @Parameter( property = "password" ) |
| private String password; |
| |
| /** |
| * The private key (used by java svn). |
| */ |
| @Parameter( property = "privateKey" ) |
| private String privateKey; |
| |
| /** |
| * The passphrase (used by java svn). |
| */ |
| @Parameter( property = "passphrase" ) |
| private String passphrase; |
| |
| /** |
| * The url of tags base directory (used by svn protocol). |
| */ |
| @Parameter( property = "tagBase" ) |
| private String tagBase; |
| |
| /** |
| * The URL to view the scm. Basis for external links from the generated report. |
| */ |
| @Parameter( property = "project.scm.url" ) |
| protected String scmUrl; |
| |
| /** |
| * Skip the Changelog report generation. Most useful on the command line |
| * via "-Dchangelog.skip=true". |
| * |
| * @since 2.3 |
| */ |
| @Parameter( property = "changelog.skip", defaultValue = "false" ) |
| protected boolean skip; |
| |
| /** |
| * Encodes slashes in file uri. Required for some repository browsers like gitblit |
| * |
| * @since 2.3 |
| */ |
| @Parameter( property = "encodeFileUri", defaultValue = "false" ) |
| protected boolean encodeFileUri; |
| |
| /** |
| * List of files to include. Specified as fileset patterns of files to include in the report |
| * |
| * @since 2.3 |
| */ |
| @Parameter |
| private String[] includes; |
| |
| /** |
| * List of files to include. Specified as fileset patterns of files to omit in the report |
| * |
| * @since 2.3 |
| */ |
| @Parameter |
| private String[] excludes; |
| |
| |
| /** |
| * The Maven Project Object |
| */ |
| @Parameter( defaultValue = "${project}", readonly = true, required = true ) |
| private MavenProject project; |
| |
| /** |
| * The directory where the report will be generated |
| */ |
| @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) |
| private File outputDirectory; |
| |
| /** |
| */ |
| @Component |
| private Renderer siteRenderer; |
| |
| /** |
| */ |
| @Parameter( property = "settings.offline", required = true, readonly = true ) |
| private boolean offline; |
| |
| /** |
| */ |
| @Component |
| private ScmManager manager; |
| |
| /** |
| */ |
| @Parameter( defaultValue = "${settings}", readonly = true, required = true ) |
| private Settings settings; |
| |
| /** |
| * Allows the user to choose which scm connection to use when connecting to the scm. |
| * Can either be "connection" or "developerConnection". |
| */ |
| @Parameter( defaultValue = "connection", required = true ) |
| private String connectionType; |
| |
| /** |
| * A template string that is used to create the URL to the file details. |
| * There is a special token that you can use in your template: |
| * <ul> |
| * <li><code>%FILE%</code> - this is the path to a file</li> |
| * </ul> |
| * <p> |
| * Example: |
| * <code>http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup</code> |
| * </p> |
| * <p> |
| * <strong>Note:</strong> If you don't supply the token in your template, |
| * the path of the file will simply be appended to your template URL. |
| * </p> |
| */ |
| @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) |
| protected String displayFileDetailUrl; |
| |
| /** |
| * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, |
| * Bugzilla and alike in the SCM commit messages. Any matched patterns |
| * are replaced with <code>issueLinkUrl<code> URL. The default |
| * value is a JIRA-style issue identification pattern. |
| * <p/> |
| * <strong>Note:</strong> Default value is [a-zA-Z]{2,}-\d+ |
| * |
| * @since 2.2 |
| */ |
| @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) |
| private String issueIDRegexPattern; |
| |
| /** |
| * The issue tracker URL used when replacing any matched <code>issueIDRegexPattern</code> |
| * found in the SCM commit messages. The default is URL is the codehaus JIRA |
| * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, |
| * otherwise the matched issue ID is appended to the URL. |
| * |
| * @since 2.2 |
| */ |
| @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) |
| private String issueLinkUrl; |
| |
| /** |
| * <p>A template string that is used to create the changeset URL. |
| * If not defined no change set link will be created. |
| * </p> |
| * There is one special token that you can use in your template: |
| * <ul> |
| * <li><code>%REV%</code> - this is the changeset revision</li> |
| * </ul> |
| * <p>Example: |
| * <code>http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV%</code> |
| * </p> |
| * <p><strong>Note:</strong> If you don't supply the %REV% token in your template, |
| * the revision will simply be appended to your template URL. |
| * </p> |
| * |
| * @since 2.2 |
| */ |
| @Parameter( property = "displayChangeSetDetailUrl" ) |
| protected String displayChangeSetDetailUrl; |
| |
| /** |
| * <p>A template string that is used to create the revision aware URL to |
| * the file details in a similar fashion to the <code>displayFileDetailUrl</code>. |
| * When a report contains both file and file revision information, as in the |
| * Change Log report, this template string can be used to create a revision |
| * aware URL to the file details. |
| * </p> |
| * <p>If not defined this template string defaults to the same value as the |
| * <code>displayFileDetailUrl</code> and thus revision number aware links will |
| * not be used. |
| * </p> |
| * There are two special tokens that you can use in your template: |
| * <ul> |
| * <li><code>%FILE%</code> - this is the path to a file</li> |
| * <li><code>%REV%</code> - this is the revision of the file</li> |
| * </ul> |
| * <p>Example: |
| * <code>http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV%</code> |
| * </p> |
| * <p><strong>Note:</strong> If you don't supply the %FILE% token in your template, |
| * the path of the file will simply be appended to your template URL. |
| * </p> |
| * |
| * @since 2.2 |
| */ |
| @Parameter( property = "displayFileRevDetailUrl" ) |
| protected String displayFileRevDetailUrl; |
| |
| /** |
| * List of developers to be shown on the report. |
| * |
| * @since 2.2 |
| */ |
| @Parameter( property = "project.developers" ) |
| protected List<Developer> developers; |
| |
| /** |
| * List of provider implementations. |
| */ |
| @Parameter |
| private Map<String, String> providerImplementations; |
| |
| // temporary field holder while generating the report |
| private String rptRepository, rptOneRepoParam, rptMultiRepoParam; |
| |
| // field for SCM Connection URL |
| private String connection; |
| |
| // field used to hold a map of the developers by Id |
| private HashMap<String, Developer> developersById; |
| |
| // field used to hold a map of the developers by Name |
| private HashMap<String, Developer> developersByName; |
| |
| /** |
| * The system properties to use (needed by the perforce scm provider). |
| */ |
| @Parameter |
| private Properties systemProperties; |
| |
| private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void executeReport( Locale locale ) |
| throws MavenReportException |
| { |
| //check if sources exists <-- required for parent poms |
| if ( !basedir.exists() ) |
| { |
| doGenerateEmptyReport( getBundle( locale ), getSink() ); |
| |
| return; |
| } |
| |
| if ( providerImplementations != null ) |
| { |
| for ( Map.Entry<String, String> entry : providerImplementations.entrySet() ) |
| { |
| String providerType = entry.getKey(); |
| String providerImplementation = entry.getValue(); |
| getLog().info( |
| "Change the default '" + providerType + "' provider implementation to '" + providerImplementation |
| + "'." ); |
| manager.setScmProviderImplementation( providerType, providerImplementation ); |
| } |
| } |
| |
| initializeDefaultConfigurationParameters(); |
| |
| initializeDeveloperMaps(); |
| |
| verifySCMTypeParams(); |
| |
| if ( systemProperties != null ) |
| { |
| // Add all system properties configured by the user |
| Iterator<?> iter = systemProperties.keySet().iterator(); |
| |
| while ( iter.hasNext() ) |
| { |
| String key = (String) iter.next(); |
| |
| String value = systemProperties.getProperty( key ); |
| |
| System.setProperty( key, value ); |
| |
| getLog().debug( "Setting system property: " + key + '=' + value ); |
| } |
| } |
| |
| doGenerateReport( getChangedSets(), getBundle( locale ), getSink() ); |
| } |
| |
| /** |
| * Initializes any configuration parameters that have not/can not be defined |
| * or defaulted by the Mojo API. |
| */ |
| private void initializeDefaultConfigurationParameters() |
| { |
| if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) |
| { |
| displayFileRevDetailUrl = displayFileDetailUrl; |
| } |
| } |
| |
| /** |
| * Creates maps of the project developers by developer Id and developer Name |
| * for quick lookups. |
| */ |
| private void initializeDeveloperMaps() |
| { |
| developersById = new HashMap<String, Developer>(); |
| developersByName = new HashMap<String, Developer>(); |
| |
| if ( developers != null ) |
| { |
| for ( Developer developer : developers ) |
| { |
| developersById.put( developer.getId(), developer ); |
| developersByName.put( developer.getName(), developer ); |
| } |
| } |
| } |
| |
| /** |
| * Populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous |
| * run of the report |
| * |
| * @return a List of ChangeLogSets |
| * @throws MavenReportException if any exception occurs |
| */ |
| protected List<ChangeLogSet> getChangedSets() |
| throws MavenReportException |
| { |
| List<ChangeLogSet> changelogList = null; |
| |
| if ( !outputXML.isAbsolute() ) |
| { |
| outputXML = new File( project.getBasedir(), outputXML.getPath() ); |
| } |
| |
| if ( outputXML.exists() ) |
| { |
| // CHECKSTYLE_OFF: MagicNumber |
| if ( outputXMLExpiration > 0 |
| && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) |
| // CHECKSTYLE_ON: MagicNumber |
| { |
| try |
| { |
| //ReaderFactory.newReader( outputXML, getOutputEncoding() ); |
| //FileInputStream fIn = new FileInputStream( outputXML ); |
| |
| getLog().info( "Using existing changelog.xml..." ); |
| |
| changelogList = |
| ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); |
| } |
| catch ( FileNotFoundException e ) |
| { |
| //do nothing, just regenerate |
| } |
| catch ( Exception e ) |
| { |
| throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), |
| e ); |
| } |
| } |
| } |
| |
| if ( changelogList == null ) |
| { |
| if ( offline ) |
| { |
| throw new MavenReportException( "This report requires online mode." ); |
| } |
| |
| getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); |
| |
| changelogList = generateChangeSetsFromSCM(); |
| |
| try |
| { |
| writeChangelogXml( changelogList ); |
| } |
| catch ( FileNotFoundException e ) |
| { |
| throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); |
| } |
| catch ( UnsupportedEncodingException e ) |
| { |
| throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); |
| } |
| catch ( IOException e ) |
| { |
| throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); |
| } |
| } |
| |
| return changelogList; |
| } |
| |
| private void writeChangelogXml( List<ChangeLogSet> changelogList ) |
| throws IOException |
| { |
| StringBuilder changelogXml = new StringBuilder(); |
| |
| changelogXml.append( "<?xml version=\"1.0\" encoding=\"" ).append( getOutputEncoding() ).append( "\"?>\n" ); |
| changelogXml.append( "<changelog>" ); |
| |
| for ( ChangeLogSet changelogSet : changelogList ) |
| { |
| changelogXml.append( "\n " ); |
| |
| String changeset = changelogSet.toXML( getOutputEncoding() ); |
| |
| //remove xml header |
| if ( changeset.startsWith( "<?xml" ) ) |
| { |
| int idx = changeset.indexOf( "?>" ) + 2; |
| changeset = changeset.substring( idx ); |
| } |
| |
| changelogXml.append( changeset ); |
| } |
| |
| changelogXml.append( "\n</changelog>" ); |
| |
| outputXML.getParentFile().mkdirs(); |
| |
| //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); |
| //pw.write( changelogXml.toString() ); |
| //pw.flush(); |
| //pw.close(); |
| // MCHANGELOG-86 |
| Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), |
| getOutputEncoding() ); |
| writer.write( changelogXml.toString() ); |
| writer.flush(); |
| writer.close(); |
| } |
| |
| /** |
| * creates a ChangeLog object and then connects to the SCM to generate the changed sets |
| * |
| * @return changedlogsets generated from the SCM |
| * @throws MavenReportException if any exception occurs |
| */ |
| protected List<ChangeLogSet> generateChangeSetsFromSCM() |
| throws MavenReportException |
| { |
| try |
| { |
| List<ChangeLogSet> changeSets = new ArrayList<ChangeLogSet>(); |
| |
| ScmRepository repository = getScmRepository(); |
| |
| ScmProvider provider = manager.getProviderByRepository( repository ); |
| |
| ChangeLogScmResult result; |
| |
| if ( "range".equals( type ) ) |
| { |
| result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, |
| dateFormat ); |
| |
| checkResult( result ); |
| |
| changeSets.add( result.getChangeLog() ); |
| } |
| else if ( "tag".equals( type ) ) |
| { |
| |
| Iterator<String> tagsIter = tags.iterator(); |
| |
| String startTag = tagsIter.next(); |
| String endTag = null; |
| |
| if ( tagsIter.hasNext() ) |
| { |
| while ( tagsIter.hasNext() ) |
| { |
| endTag = tagsIter.next(); |
| String endRevision = getRevisionForTag( endTag, repository, provider ); |
| String startRevision = getRevisionForTag( startTag, repository, provider ); |
| result = provider.changeLog( repository, new ScmFileSet( basedir ), |
| new ScmRevision( startRevision ), |
| new ScmRevision( endRevision ) ); |
| |
| checkResult( result ); |
| result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); |
| result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); |
| |
| changeSets.add( result.getChangeLog() ); |
| |
| startTag = endTag; |
| } |
| } |
| else |
| { |
| String startRevision = getRevisionForTag( startTag, repository, provider ); |
| String endRevision = getRevisionForTag( endTag, repository, provider ); |
| result = provider.changeLog( repository, new ScmFileSet( basedir ), |
| new ScmRevision( startRevision ), |
| new ScmRevision( endRevision ) ); |
| |
| checkResult( result ); |
| result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); |
| result.getChangeLog().setEndVersion( null ); |
| changeSets.add( result.getChangeLog() ); |
| } |
| } |
| else if ( "date".equals( type ) ) |
| { |
| Iterator<String> dateIter = dates.iterator(); |
| |
| String startDate = dateIter.next(); |
| String endDate = null; |
| |
| if ( dateIter.hasNext() ) |
| { |
| while ( dateIter.hasNext() ) |
| { |
| endDate = dateIter.next(); |
| |
| result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), |
| parseDate( endDate ), 0, (ScmBranch) null ); |
| |
| checkResult( result ); |
| |
| changeSets.add( result.getChangeLog() ); |
| |
| startDate = endDate; |
| } |
| } |
| else |
| { |
| result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), |
| parseDate( endDate ), 0, (ScmBranch) null ); |
| |
| checkResult( result ); |
| |
| changeSets.add( result.getChangeLog() ); |
| } |
| } |
| else |
| { |
| throw new MavenReportException( "The type '" + type + "' isn't supported." ); |
| } |
| filter( changeSets ); |
| return changeSets; |
| |
| } |
| catch ( ScmException e ) |
| { |
| throw new MavenReportException( "Cannot run changelog command : ", e ); |
| } |
| catch ( MojoExecutionException e ) |
| { |
| throw new MavenReportException( "An error has occurred during changelog command : ", e ); |
| } |
| } |
| |
| /** |
| * Resolves the given tag to the revision number. |
| * |
| * @param tag |
| * @param repository |
| * @param provider |
| * @return |
| * @throws ScmException |
| */ |
| private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) |
| throws ScmException |
| { |
| if ( repository.getProvider().equals( "svn" ) ) |
| { |
| if ( tag == null ) |
| { |
| return "HEAD"; |
| } |
| SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); |
| infoCommand.setLogger( new DefaultLog() ); |
| |
| InfoScmResult infoScmResult = |
| infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), |
| new ScmFileSet( basedir ), tag, null, false, null ); |
| if ( infoScmResult.getInfoItems().size() == 0 ) |
| { |
| throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); |
| } |
| InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); |
| String revision = infoItem.getLastChangedRevision(); |
| getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); |
| return revision; |
| } |
| return tag; |
| } |
| |
| /** |
| * filters out unwanted files from the changesets |
| */ |
| private void filter( List<ChangeLogSet> changeSets ) |
| { |
| List<Pattern> include = compilePatterns( includes ); |
| List<Pattern> exclude = compilePatterns( excludes ); |
| if ( includes == null && excludes == null ) |
| { |
| return; |
| } |
| for ( ChangeLogSet changeLogSet : changeSets ) |
| { |
| List<ChangeSet> set = changeLogSet.getChangeSets(); |
| filter( set, include, exclude ); |
| } |
| |
| } |
| |
| private List<Pattern> compilePatterns( String[] patternArray ) |
| { |
| if ( patternArray == null ) |
| { |
| return new ArrayList<Pattern>(); |
| } |
| List<Pattern> patterns = new ArrayList<Pattern>( patternArray.length ); |
| for ( String string : patternArray ) |
| { |
| //replaces * with [/\]* (everything but file seperators) |
| //replaces ** with .* |
| //quotes the rest of the string |
| string = "\\Q" + string + "\\E"; |
| string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); |
| string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); |
| string = string.replace( "?REPLACEMENT?", "*" ); |
| string = string.replace( "\\Q\\E", "" ); |
| patterns.add( Pattern.compile( string ) ); |
| } |
| return patterns; |
| } |
| |
| private void filter( List<ChangeSet> sets, List<Pattern> includes, List<Pattern> excludes ) |
| { |
| Iterator<ChangeSet> it = sets.iterator(); |
| while ( it.hasNext() ) |
| { |
| ChangeSet changeSet = it.next(); |
| List<ChangeFile> files = changeSet.getFiles(); |
| Iterator<ChangeFile> iterator = files.iterator(); |
| while ( iterator.hasNext() ) |
| { |
| ChangeFile changeFile = iterator.next(); |
| String name = changeFile.getName(); |
| if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) |
| { |
| iterator.remove(); |
| } |
| } |
| if ( files.isEmpty() ) |
| { |
| it.remove(); |
| } |
| } |
| } |
| |
| private boolean isExcluded( List<Pattern> excludes, String name ) |
| { |
| if ( excludes == null || excludes.isEmpty() ) |
| { |
| return false; |
| } |
| for ( Pattern pattern : excludes ) |
| { |
| if ( pattern.matcher( name ).matches() ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isIncluded( List<Pattern> includes, String name ) |
| { |
| if ( includes == null || includes.isEmpty() ) |
| { |
| return true; |
| } |
| for ( Pattern pattern : includes ) |
| { |
| if ( pattern.matcher( name ).matches() ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Converts the localized date string pattern to date object. |
| * |
| * @return A date |
| */ |
| private Date parseDate( String date ) |
| throws MojoExecutionException |
| { |
| if ( date == null || date.trim().length() == 0 ) |
| { |
| return null; |
| } |
| |
| SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); |
| |
| try |
| { |
| return formatter.parse( date ); |
| } |
| catch ( ParseException e ) |
| { |
| throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); |
| } |
| } |
| |
| public ScmRepository getScmRepository() |
| throws ScmException |
| { |
| ScmRepository repository; |
| |
| try |
| { |
| repository = manager.makeScmRepository( getConnection() ); |
| |
| ScmProviderRepository providerRepo = repository.getProviderRepository(); |
| |
| if ( !StringUtils.isEmpty( username ) ) |
| { |
| providerRepo.setUser( username ); |
| } |
| |
| if ( !StringUtils.isEmpty( password ) ) |
| { |
| providerRepo.setPassword( password ); |
| } |
| |
| if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) |
| { |
| ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); |
| |
| loadInfosFromSettings( repo ); |
| |
| if ( !StringUtils.isEmpty( username ) ) |
| { |
| repo.setUser( username ); |
| } |
| |
| if ( !StringUtils.isEmpty( password ) ) |
| { |
| repo.setPassword( password ); |
| } |
| |
| if ( !StringUtils.isEmpty( privateKey ) ) |
| { |
| repo.setPrivateKey( privateKey ); |
| } |
| |
| if ( !StringUtils.isEmpty( passphrase ) ) |
| { |
| repo.setPassphrase( passphrase ); |
| } |
| } |
| |
| if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) |
| { |
| SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); |
| |
| svnRepo.setTagBase( tagBase ); |
| } |
| } |
| catch ( Exception e ) |
| { |
| throw new ScmException( "Can't load the scm provider.", e ); |
| } |
| |
| return repository; |
| } |
| |
| /** |
| * Load username password from settings if user has not set them in JVM properties |
| * |
| * @param repo |
| */ |
| private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) |
| { |
| if ( username == null || password == null ) |
| { |
| String host = repo.getHost(); |
| |
| int port = repo.getPort(); |
| |
| if ( port > 0 ) |
| { |
| host += ":" + port; |
| } |
| |
| Server server = this.settings.getServer( host ); |
| |
| if ( server != null ) |
| { |
| if ( username == null ) |
| { |
| username = this.settings.getServer( host ).getUsername(); |
| } |
| |
| if ( password == null ) |
| { |
| password = this.settings.getServer( host ).getPassword(); |
| } |
| |
| if ( privateKey == null ) |
| { |
| privateKey = this.settings.getServer( host ).getPrivateKey(); |
| } |
| |
| if ( passphrase == null ) |
| { |
| passphrase = this.settings.getServer( host ).getPassphrase(); |
| } |
| } |
| } |
| } |
| |
| public void checkResult( ScmResult result ) |
| throws MojoExecutionException |
| { |
| if ( !result.isSuccess() ) |
| { |
| getLog().error( "Provider message:" ); |
| |
| getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); |
| |
| getLog().error( "Command output:" ); |
| |
| getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); |
| |
| throw new MojoExecutionException( "Command failed." ); |
| } |
| } |
| |
| /** |
| * used to retrieve the SCM connection string |
| * |
| * @return the url string used to connect to the SCM |
| * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string |
| */ |
| protected String getConnection() |
| throws MavenReportException |
| { |
| if ( this.connection != null ) |
| { |
| return connection; |
| } |
| |
| if ( project.getScm() == null ) |
| { |
| throw new MavenReportException( "SCM Connection is not set." ); |
| } |
| |
| String scmConnection = project.getScm().getConnection(); |
| if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) |
| { |
| connection = scmConnection; |
| } |
| |
| String scmDeveloper = project.getScm().getDeveloperConnection(); |
| if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) |
| { |
| connection = scmDeveloper; |
| } |
| |
| if ( StringUtils.isEmpty( connection ) ) |
| { |
| throw new MavenReportException( "SCM Connection is not set." ); |
| } |
| |
| return connection; |
| } |
| |
| /** |
| * checks whether there are enough configuration parameters to generate the report |
| * |
| * @throws MavenReportException when there is insufficient paramters to generate the report |
| */ |
| private void verifySCMTypeParams() |
| throws MavenReportException |
| { |
| if ( "range".equals( type ) ) |
| { |
| if ( range == -1 ) |
| { |
| range = DEFAULT_RANGE; |
| } |
| } |
| else if ( "date".equals( type ) ) |
| { |
| if ( dates == null ) |
| { |
| throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " |
| + "should be the absolute date for the start of the log." ); |
| } |
| } |
| else if ( "tag".equals( type ) ) |
| { |
| if ( tags == null ) |
| { |
| throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); |
| } |
| } |
| else |
| { |
| throw new MavenReportException( "The type parameter has an invalid value: " + type |
| + ". The value should be \"range\", \"date\", or \"tag\"." ); |
| } |
| } |
| |
| /** |
| * generates an empty report in case there are no sources to generate a report with |
| * |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) |
| { |
| sink.head(); |
| sink.title(); |
| sink.text( bundle.getString( "report.changelog.header" ) ); |
| sink.title_(); |
| sink.head_(); |
| |
| sink.body(); |
| sink.section1(); |
| |
| sink.sectionTitle1(); |
| sink.text( bundle.getString( "report.changelog.mainTitle" ) ); |
| sink.sectionTitle1_(); |
| |
| sink.paragraph(); |
| sink.text( bundle.getString( "report.changelog.nosources" ) ); |
| sink.paragraph_(); |
| |
| sink.section1_(); |
| |
| sink.body_(); |
| sink.flush(); |
| sink.close(); |
| } |
| |
| /** |
| * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo |
| * |
| * @param changeLogSets changed sets to generate the report from |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| protected void doGenerateReport( List<ChangeLogSet> changeLogSets, ResourceBundle bundle, Sink sink ) |
| { |
| sink.head(); |
| sink.title(); |
| sink.text( bundle.getString( "report.changelog.header" ) ); |
| sink.title_(); |
| sink.head_(); |
| |
| sink.body(); |
| sink.section1(); |
| |
| sink.sectionTitle1(); |
| sink.text( bundle.getString( "report.changelog.mainTitle" ) ); |
| sink.sectionTitle1_(); |
| |
| // Summary section |
| doSummarySection( changeLogSets, bundle, sink ); |
| |
| for ( ChangeLogSet changeLogSet : changeLogSets ) |
| { |
| doChangedSet( changeLogSet, bundle, sink ); |
| } |
| |
| sink.section1_(); |
| sink.body_(); |
| |
| sink.flush(); |
| sink.close(); |
| } |
| |
| /** |
| * generates the report summary section of the report |
| * |
| * @param changeLogSets changed sets to generate the report from |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| private void doSummarySection( List<ChangeLogSet> changeLogSets, ResourceBundle bundle, Sink sink ) |
| { |
| sink.paragraph(); |
| |
| sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); |
| sink.text( ": " + changeLogSets.size() ); |
| |
| sink.paragraph_(); |
| } |
| |
| /** |
| * generates a section of the report referring to a changeset |
| * |
| * @param set the current ChangeSet to generate this section of the report |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) |
| { |
| sink.section2(); |
| |
| doChangeSetTitle( set, bundle, sink ); |
| |
| doSummary( set, bundle, sink ); |
| |
| doChangedSetTable( set.getChangeSets(), bundle, sink ); |
| |
| sink.section2_(); |
| } |
| |
| /** |
| * Generate the title for the report. |
| * |
| * @param set change set to generate the report from |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) |
| { |
| sink.sectionTitle2(); |
| |
| SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); |
| |
| if ( "tag".equals( type ) ) |
| { |
| if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) |
| { |
| sink.text( bundle.getString( "report.SetTagCreation" ) ); |
| if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) |
| { |
| sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); |
| } |
| } |
| else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) |
| { |
| sink.text( bundle.getString( "report.SetTagSince" ) ); |
| sink.text( " '" + set.getStartVersion() + '\'' ); |
| } |
| else |
| { |
| sink.text( bundle.getString( "report.SetTagBetween" ) ); |
| sink.text( |
| " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() |
| + '\'' ); |
| } |
| } |
| else if ( set.getStartDate() == null ) |
| { |
| sink.text( bundle.getString( "report.SetRangeUnknown" ) ); |
| } |
| else if ( set.getEndDate() == null ) |
| { |
| sink.text( bundle.getString( "report.SetRangeSince" ) ); |
| sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); |
| } |
| else |
| { |
| sink.text( bundle.getString( "report.SetRangeBetween" ) ); |
| sink.text( |
| ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' |
| + headingDateFormater.format( set.getEndDate() ) ); |
| } |
| sink.sectionTitle2_(); |
| } |
| |
| /** |
| * Generate the summary section of the report. |
| * |
| * @param set change set to generate the report from |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) |
| { |
| sink.paragraph(); |
| sink.text( bundle.getString( "report.TotalCommits" ) ); |
| sink.text( ": " + set.getChangeSets().size() ); |
| sink.lineBreak(); |
| sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); |
| sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); |
| sink.paragraph_(); |
| } |
| |
| /** |
| * counts the number of files that were changed in the specified SCM |
| * |
| * @param entries a collection of SCM changes |
| * @return number of files changed for the changedsets |
| */ |
| protected long countFilesChanged( Collection<ChangeSet> entries ) |
| { |
| if ( entries == null ) |
| { |
| return 0; |
| } |
| |
| if ( entries.isEmpty() ) |
| { |
| return 0; |
| } |
| |
| HashMap<String, List<ChangeFile>> fileList = new HashMap<String, List<ChangeFile>>(); |
| |
| for ( ChangeSet entry : entries ) |
| { |
| for ( ChangeFile file : entry.getFiles() ) |
| { |
| String name = file.getName(); |
| List<ChangeFile> list = fileList.get( name ); |
| |
| if ( list != null ) |
| { |
| list.add( file ); |
| } |
| else |
| { |
| list = new LinkedList<ChangeFile>(); |
| |
| list.add( file ); |
| |
| fileList.put( name, list ); |
| } |
| } |
| } |
| |
| return fileList.size(); |
| } |
| |
| /** |
| * generates the report table showing the SCM log entries |
| * |
| * @param entries a list of change log entries to generate the report from |
| * @param bundle the resource bundle to retrieve report phrases from |
| * @param sink the report formatting tool |
| */ |
| private void doChangedSetTable( Collection<ChangeSet> entries, ResourceBundle bundle, Sink sink ) |
| { |
| sink.table(); |
| |
| sink.tableRow(); |
| sink.tableHeaderCell(); |
| sink.text( bundle.getString( "report.changelog.timestamp" ) ); |
| sink.tableHeaderCell_(); |
| sink.tableHeaderCell(); |
| sink.text( bundle.getString( "report.changelog.author" ) ); |
| sink.tableHeaderCell_(); |
| sink.tableHeaderCell(); |
| sink.text( bundle.getString( "report.changelog.details" ) ); |
| sink.tableHeaderCell_(); |
| sink.tableRow_(); |
| |
| initReportUrls(); |
| |
| List<ChangeSet> sortedEntries = new ArrayList<ChangeSet>( entries ); |
| Collections.sort( sortedEntries, new Comparator<ChangeSet>() |
| { |
| public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) |
| { |
| return changeSet1.getDate().compareTo( changeSet0.getDate() ); |
| } |
| } ); |
| |
| for ( ChangeSet entry : sortedEntries ) |
| { |
| doChangedSetDetail( entry, sink ); |
| } |
| |
| sink.table_(); |
| } |
| |
| /** |
| * reports on the details of an SCM entry log |
| * |
| * @param entry an SCM entry to generate the report from |
| * @param sink the report formatting tool |
| */ |
| private void doChangedSetDetail( ChangeSet entry, Sink sink ) |
| { |
| sink.tableRow(); |
| |
| sink.tableCell(); |
| sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); |
| sink.tableCell_(); |
| |
| sink.tableCell(); |
| |
| sinkAuthorDetails( sink, entry.getAuthor() ); |
| |
| sink.tableCell_(); |
| |
| sink.tableCell(); |
| //doRevision( entry.getFiles(), bundle, sink ); |
| doChangedFiles( entry.getFiles(), sink ); |
| sink.lineBreak(); |
| StringReader sr = new StringReader( entry.getComment() ); |
| BufferedReader br = new BufferedReader( sr ); |
| String line; |
| try |
| { |
| if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null |
| && issueLinkUrl.length() > 0 ) ) |
| { |
| Pattern pattern = Pattern.compile( issueIDRegexPattern ); |
| |
| line = br.readLine(); |
| |
| while ( line != null ) |
| { |
| sinkIssueLink( sink, line, pattern ); |
| |
| line = br.readLine(); |
| if ( line != null ) |
| { |
| sink.lineBreak(); |
| } |
| } |
| } |
| else |
| { |
| line = br.readLine(); |
| |
| while ( line != null ) |
| { |
| sink.text( line ); |
| line = br.readLine(); |
| if ( line != null ) |
| { |
| sink.lineBreak(); |
| } |
| } |
| } |
| } |
| catch ( IOException e ) |
| { |
| getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); |
| } |
| finally |
| { |
| try |
| { |
| br.close(); |
| } |
| catch ( IOException e ) |
| { |
| getLog().warn( "Unable to close a reader." ); |
| } |
| sr.close(); |
| } |
| sink.tableCell_(); |
| |
| sink.tableRow_(); |
| } |
| |
| private void sinkIssueLink( Sink sink, String line, Pattern pattern ) |
| { |
| // replace any ticket patterns found. |
| |
| Matcher matcher = pattern.matcher( line ); |
| |
| int currLoc = 0; |
| |
| while ( matcher.find() ) |
| { |
| String match = matcher.group(); |
| |
| String link; |
| |
| if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) |
| { |
| link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); |
| } |
| else |
| { |
| if ( issueLinkUrl.endsWith( "/" ) ) |
| { |
| link = issueLinkUrl; |
| } |
| else |
| { |
| link = issueLinkUrl + '/'; |
| } |
| |
| link += match; |
| } |
| |
| int startOfMatch = matcher.start(); |
| |
| String unmatchedText = line.substring( currLoc, startOfMatch ); |
| |
| currLoc = matcher.end(); |
| |
| sink.text( unmatchedText ); |
| |
| sink.link( link ); |
| sink.text( match ); |
| sink.link_(); |
| } |
| |
| sink.text( line.substring( currLoc ) ); |
| } |
| |
| /** |
| * If the supplied author is a known developer this method outputs a |
| * link to the team members report, or alternatively, if the supplied |
| * author is unknown, outputs the author's name as plain text. |
| * |
| * @param sink Sink to use for outputting |
| * @param author The author's name. |
| */ |
| protected void sinkAuthorDetails( Sink sink, String author ) |
| { |
| Developer developer = developersById.get( author ); |
| |
| if ( developer == null ) |
| { |
| developer = developersByName.get( author ); |
| } |
| |
| if ( developer != null ) |
| { |
| sink.link( "team-list.html#" + developer.getId() ); |
| sink.text( developer.getName() ); |
| sink.link_(); |
| } |
| else |
| { |
| sink.text( author ); |
| } |
| } |
| |
| /** |
| * populates the report url used to create links from certain elements of the report |
| */ |
| protected void initReportUrls() |
| { |
| if ( scmUrl != null ) |
| { |
| int idx = scmUrl.indexOf( '?' ); |
| |
| if ( idx > 0 ) |
| { |
| rptRepository = scmUrl.substring( 0, idx ); |
| |
| if ( scmUrl.equals( displayFileDetailUrl ) ) |
| { |
| String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); |
| |
| rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); |
| |
| rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); |
| } |
| } |
| else |
| { |
| rptRepository = scmUrl; |
| |
| rptOneRepoParam = ""; |
| |
| rptMultiRepoParam = ""; |
| } |
| } |
| } |
| |
| /** |
| * generates the section of the report listing all the files revisions |
| * |
| * @param files list of files to generate the reports from |
| * @param sink the report formatting tool |
| */ |
| private void doChangedFiles( List<ChangeFile> files, Sink sink ) |
| { |
| for ( ChangeFile file : files ) |
| { |
| sinkLogFile( sink, file.getName(), file.getRevision() ); |
| } |
| } |
| |
| /** |
| * generates the section of the report detailing the revisions made and the files changed |
| * |
| * @param sink the report formatting tool |
| * @param name filename of the changed file |
| * @param revision the revision code for this file |
| */ |
| private void sinkLogFile( Sink sink, String name, String revision ) |
| { |
| try |
| { |
| String connection = getConnection(); |
| |
| generateLinks( connection, name, revision, sink ); |
| } |
| catch ( Exception e ) |
| { |
| getLog().debug( e ); |
| |
| sink.text( name + " v " + revision ); |
| } |
| |
| sink.lineBreak(); |
| } |
| |
| /** |
| * attaches the http links from the changed files |
| * |
| * @param connection the string used to connect to the SCM |
| * @param name filename of the file that was changed |
| * @param sink the report formatting tool |
| */ |
| protected void generateLinks( String connection, String name, Sink sink ) |
| { |
| generateLinks( connection, name, null, sink ); |
| } |
| |
| /** |
| * attaches the http links from the changed files |
| * |
| * @param connection the string used to connect to the SCM |
| * @param name filename of the file that was changed |
| * @param revision the revision code |
| * @param sink the report formatting tool |
| */ |
| protected void generateLinks( String connection, String name, String revision, Sink sink ) |
| { |
| String linkFile; |
| String linkRev = null; |
| |
| if ( revision != null ) |
| { |
| linkFile = displayFileRevDetailUrl; |
| } |
| else |
| { |
| linkFile = displayFileDetailUrl; |
| } |
| |
| if ( linkFile != null ) |
| { |
| if ( !linkFile.equals( scmUrl ) ) |
| { |
| String linkName = name; |
| if ( encodeFileUri ) |
| { |
| try |
| { |
| linkName = URLEncoder.encode( linkName, "UTF-8" ); |
| } |
| catch ( UnsupportedEncodingException e ) |
| { |
| // UTF-8 is always supported |
| } |
| } |
| |
| // Use the given URL to create links to the files |
| |
| if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) |
| { |
| linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); |
| } |
| else |
| { |
| // This is here so that we are backwards compatible with the |
| // format used before the special token was introduced |
| |
| linkFile += linkName; |
| } |
| |
| // Use the given URL to create links to the files |
| |
| if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) |
| { |
| linkFile = linkFile.replaceAll( REV_TOKEN, revision ); |
| } |
| } |
| else if ( connection.startsWith( "scm:perforce" ) ) |
| { |
| String path = getAbsolutePath( displayFileDetailUrl, name ); |
| linkFile = path + "?ac=22"; |
| if ( revision != null ) |
| { |
| linkRev = path + "?ac=64&rev=" + revision; |
| } |
| } |
| else if ( connection.startsWith( "scm:clearcase" ) ) |
| { |
| String path = getAbsolutePath( displayFileDetailUrl, name ); |
| linkFile = path + rptOneRepoParam; |
| } |
| else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) |
| { |
| Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); |
| String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); |
| linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; |
| if ( revision != null ) |
| { |
| linkRev = |
| rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; |
| } |
| } |
| else |
| { |
| String path = getAbsolutePath( displayFileDetailUrl, name ); |
| linkFile = path + rptOneRepoParam; |
| if ( revision != null ) |
| { |
| linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; |
| } |
| } |
| } |
| |
| if ( linkFile != null ) |
| { |
| sink.link( linkFile ); |
| sinkFileName( name, sink ); |
| sink.link_(); |
| } |
| else |
| { |
| sinkFileName( name, sink ); |
| } |
| |
| sink.text( " " ); |
| |
| if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) |
| { |
| if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) |
| { |
| linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); |
| } |
| else |
| { |
| linkRev = displayChangeSetDetailUrl + revision; |
| } |
| } |
| |
| if ( linkRev != null ) |
| { |
| sink.link( linkRev ); |
| sink.text( "v " + revision ); |
| sink.link_(); |
| } |
| else if ( revision != null ) |
| { |
| sink.text( "v " + revision ); |
| } |
| } |
| |
| /** |
| * Encapsulates the logic for rendering the name with a bolded markup. |
| * |
| * @param name filename of the file that was changed |
| * @param sink the report formatting tool |
| */ |
| private void sinkFileName( String name, Sink sink ) |
| { |
| name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); |
| int pos = name.lastIndexOf( '/' ); |
| |
| String head; |
| String tail; |
| if ( pos < 0 ) |
| { |
| head = ""; |
| tail = name; |
| } |
| else |
| { |
| head = name.substring( 0, pos ) + '/'; |
| tail = name.substring( pos + 1 ); |
| } |
| |
| sink.text( head ); |
| sink.bold(); |
| sink.text( tail ); |
| sink.bold_(); |
| } |
| |
| /** |
| * calculates the path from a base directory to a target file |
| * |
| * @param base base directory to create the absolute path from |
| * @param target target file to create the absolute path to |
| */ |
| private String getAbsolutePath( final String base, final String target ) |
| { |
| String absPath = ""; |
| |
| StringTokenizer baseTokens = |
| new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); |
| |
| StringTokenizer targetTokens = |
| new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); |
| |
| String targetRoot = targetTokens.nextToken(); |
| |
| while ( baseTokens.hasMoreTokens() ) |
| { |
| String baseToken = baseTokens.nextToken(); |
| |
| if ( baseToken.equals( targetRoot ) ) |
| { |
| break; |
| } |
| |
| absPath += baseToken; |
| } |
| |
| if ( !absPath.endsWith( "/" ) ) |
| { |
| absPath += "/"; |
| } |
| |
| String newTarget = target; |
| if ( newTarget.startsWith( "/" ) ) |
| { |
| newTarget = newTarget.substring( 1 ); |
| } |
| |
| return absPath + newTarget; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected MavenProject getProject() |
| { |
| return project; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected String getOutputDirectory() |
| { |
| if ( !outputDirectory.isAbsolute() ) |
| { |
| outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); |
| } |
| |
| return outputDirectory.getAbsolutePath(); |
| } |
| |
| /** |
| * Gets the effective reporting output files encoding. |
| * |
| * @return The effective reporting output file encoding, never <code>null</code>. |
| */ |
| protected String getOutputEncoding() |
| { |
| return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected Renderer getSiteRenderer() |
| { |
| return siteRenderer; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getDescription( Locale locale ) |
| { |
| return getBundle( locale ).getString( "report.changelog.description" ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getName( Locale locale ) |
| { |
| return getBundle( locale ).getString( "report.changelog.name" ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getOutputName() |
| { |
| return "changelog"; |
| } |
| |
| /** |
| * @param locale the locale for the <code>scm-activity</code> resource bundle |
| * @return the current bundle |
| */ |
| protected ResourceBundle getBundle( Locale locale ) |
| { |
| return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean canGenerateReport() |
| { |
| if ( offline && !outputXML.exists() ) |
| { |
| return false; |
| } |
| |
| return !skip; |
| |
| } |
| } |