All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oe.maven.plugins.revision.RevisionMojo Maven / Gradle / Ivy

Go to download

This plugin retrieves the revision number and the status of the Subversion working copy directory.

There is a newer version: 1.7
Show newest version
/*-
 * Copyright (c) 2009-2010, Oleg Estekhin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the distribution.
 *  * Neither the names of the copyright holders nor the names of their
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

package oe.maven.plugins.revision;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusClient;
import org.tmatesoft.svn.core.wc.SVNStatusType;

/**
 * Retrieves the revision number and the status of a file or a directory under the Subversion version control.
 *
 * @goal revision
 * @phase initialize
 * @requiresProject
 */
public class RevisionMojo extends AbstractMojo {

    static {
        DAVRepositoryFactory.setup(); // http, https
        SVNRepositoryFactoryImpl.setup(); // svn, svn+xxx
        FSRepositoryFactory.setup(); // file
    }


    /**
     * The maven project.
     *
     * @parameter expression="${project}"
     * @readonly
     */
    private MavenProject project;

    /**
     * Specifies the list of entries to inspect. Each entry has a separate configuration consisting of the local path,
     * report options and the prefix for the output properties.
     * 

* The following example shows the entry configured with the default properties: *

     * <entries>
     *   <entry>
     *     <path>${project.basedir}<path>
     *     <prefix>${project.artifactId}<prefix>
     *     <depth>infinity<depth>
     *     <reportUnversioned>true<reportUnversioned>
     *     <reportIgnored>false<reportIgnored>
     *     <reportOutOfDate>false<reportOutOfDate>
     *   <entry>
     * <entries>
     * 
*

* If entries configuration is not specified then the goal will operate on the default entry with the entry path * equal to the project basedir and the properties prefix equal to the project artifactId. * * @parameter */ private Entry[] entries; /** * Specifies whether the goal runs in verbose mode. * * @parameter */ private boolean verbose; public void execute() throws MojoExecutionException, MojoFailureException { if ( entries == null ) { logDebug( "the entries configuration is missing, using default values" ); // defaulting to the path = project.basedir, prefix = project.artifactId, depth = infinity, reporting unversioned entries = new Entry[] { new Entry( project.getBasedir(), project.getArtifactId() ), }; } else if ( entries.length == 0 ) { throw new MojoExecutionException( "the entries configuration list is empty" ); } SVNStatusClient statusClient = SVNClientManager.newInstance().getStatusClient(); for ( Entry entry : entries ) { if ( entry.getPath() == null ) { logDebug( "the entry path is not specified, using project.basedir: " + project.getBasedir() ); entry.setPath( project.getBasedir() ); } logInfo( "inspecting " + entry.getPath() ); if ( entry.getPrefix() == null ) { logDebug( "the entry prefix is not specified, using project.artifactId: " + project.getArtifactId() ); entry.setPrefix( project.getArtifactId() ); } logDebugInfo( " prefix = " + entry.getPrefix() ); logDebugInfo( " depth = " + entry.getDepth() ); logDebugInfo( " report unversioned = " + entry.reportUnversioned() ); logDebugInfo( " report ignored = " + entry.reportIgnored() ); logDebugInfo( " report out-of-date = " + entry.reportOutOfDate() ); entry.validate(); Map entryProperties = getEntryProperties( entry, statusClient ); setProjectProperties( entry.getPrefix(), entryProperties ); } } private Map getEntryProperties( Entry entry, SVNStatusClient statusClient ) throws MojoExecutionException { try { SVNStatus status = statusClient.doStatus( entry.getPath(), false ); return createVersionedEntryProperties( entry, status, statusClient ); } catch ( SVNException e ) { SVNErrorCode errorCode = e.getErrorMessage() == null ? null : e.getErrorMessage().getErrorCode(); if ( SVNErrorCode.WC_NOT_DIRECTORY.equals( errorCode ) ) { if ( getLog().isDebugEnabled() ) { getLog().debug( e ); } return createUnversionedEntryProperties(); } else { if ( getLog().isErrorEnabled() ) { getLog().error( e ); } return createSpecialEntryProperties( entry, statusClient ); } } } private Map createUnversionedEntryProperties() { Map properties = new LinkedHashMap(); properties.put( "repository", "" ); properties.put( "path", "" ); properties.put( "revision", -1L ); properties.put( "mixedRevisions", "false" ); properties.put( "committedRevision", -1L ); properties.put( "status", EntryStatusSymbols.DEFAULT.getStatusSymbol( SVNStatusType.STATUS_UNVERSIONED ) ); properties.put( "specialStatus", EntryStatusSymbols.SPECIAL.getStatusSymbol( SVNStatusType.STATUS_UNVERSIONED ) ); return properties; } private Map createVersionedEntryProperties( Entry entry, SVNStatus status, SVNStatusClient statusClient ) throws MojoExecutionException { VersionedEntryStatusHandler entryStatusHandler = new VersionedEntryStatusHandler(); entryStatusHandler.setRepositoryProperties( status.getEntry() ); try { logDebugInfo( " collecting status information" ); statusClient.doStatus( entry.getPath(), SVNRevision.WORKING, SVNDepth.fromString( entry.getDepth() ), entry.reportOutOfDate(), true, entry.reportIgnored(), false, entryStatusHandler, null ); } catch ( SVNException e ) { throw new MojoExecutionException( e.getMessage(), e ); } Map properties = new LinkedHashMap(); properties.put( "repository", entryStatusHandler.getRepositoryRoot() ); properties.put( "path", entryStatusHandler.getRepositoryPath() ); properties.put( "revision", entryStatusHandler.getMaximumRevisionNumber() ); properties.put( "mixedRevisions", entryStatusHandler.isMixedRevisions() ); properties.put( "committedRevision", entryStatusHandler.getMaximumCommittedRevisionNumber() ); properties.put( "status", createStatusString( entry, entryStatusHandler.getLocalStatusTypes(), entryStatusHandler.getRemoteStatusTypes(), EntryStatusSymbols.DEFAULT ) ); properties.put( "specialStatus", createStatusString( entry, entryStatusHandler.getLocalStatusTypes(), entryStatusHandler.getRemoteStatusTypes(), EntryStatusSymbols.SPECIAL ) ); return properties; } private Map createSpecialEntryProperties( final Entry entry, SVNStatusClient statusClient ) throws MojoExecutionException { // hack: obtain the status of a single "problem" entry by asking the entry's parent SpecialEntryStatusHandler entryStatusHandler = new SpecialEntryStatusHandler( entry.getPath().getName() ); try { logDebugInfo( " collecting status information from the entry parent" ); statusClient.doStatus( entry.getPath().getParentFile(), SVNRevision.WORKING, SVNDepth.IMMEDIATES, false, true, entry.reportIgnored(), false, entryStatusHandler, null ); } catch ( SVNException e ) { throw new MojoExecutionException( e.getMessage(), e ); } Map properties = new LinkedHashMap(); properties.put( "repository", entryStatusHandler.getRepositoryRoot() ); properties.put( "path", entryStatusHandler.getRepositoryPath() ); properties.put( "revision", entryStatusHandler.getMaximumRevisionNumber() ); properties.put( "mixedRevisions", entryStatusHandler.isMixedRevisions() ); properties.put( "committedRevision", entryStatusHandler.getMaximumCommittedRevisionNumber() ); properties.put( "status", createStatusString( entry, entryStatusHandler.getLocalStatusTypes(), entryStatusHandler.getRemoteStatusTypes(), EntryStatusSymbols.DEFAULT ) ); properties.put( "specialStatus", createStatusString( entry, entryStatusHandler.getLocalStatusTypes(), entryStatusHandler.getRemoteStatusTypes(), EntryStatusSymbols.SPECIAL ) ); return properties; } private void setProjectProperties( String prefix, Map entryProperties ) { logDebugInfo( " setting properties" ); for ( Map.Entry entryProperty : entryProperties.entrySet() ) { setProjectProperty( prefix + '.' + entryProperty.getKey(), String.valueOf( entryProperty.getValue() ) ); } } private void setProjectProperty( String name, String value ) { Properties projectProperties = project.getProperties(); if ( projectProperties.getProperty( name ) != null ) { logWarning( "the \"" + name + "\" property is already defined, its value will be overwritten. Consider another value for the entry properties prefix." ); } projectProperties.setProperty( name, value ); logDebugInfo( " " + name + " = " + value ); } private String createStatusString( Entry entry, Set localStatusTypes, Set remoteStatusTypes, EntryStatusSymbols symbols ) { StringBuilder status = new StringBuilder(); localStatusTypes.remove( SVNStatusType.STATUS_NONE ); localStatusTypes.remove( SVNStatusType.STATUS_NORMAL ); if ( localStatusTypes.remove( SVNStatusType.STATUS_ADDED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_ADDED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_CONFLICTED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_CONFLICTED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_DELETED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_DELETED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_IGNORED ) && entry.reportIgnored() ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_IGNORED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_MODIFIED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_MODIFIED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_REPLACED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_REPLACED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_EXTERNAL ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_EXTERNAL ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_UNVERSIONED ) && entry.reportUnversioned() ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_UNVERSIONED ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_MISSING ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_MISSING ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_INCOMPLETE ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_INCOMPLETE ) ); } if ( localStatusTypes.remove( SVNStatusType.STATUS_OBSTRUCTED ) ) { status.append( symbols.getStatusSymbol( SVNStatusType.STATUS_OBSTRUCTED ) ); } if ( !localStatusTypes.isEmpty() ) { // future proofing logWarning( "the following svn statuses are not taken into account: " + localStatusTypes ); } remoteStatusTypes.remove( SVNStatusType.STATUS_NONE ); if ( !remoteStatusTypes.isEmpty() && entry.reportOutOfDate() ) { status.append( symbols.getOutOfDateSymbol() ); } return status.toString(); } private void logInfo( CharSequence message ) { if ( getLog().isInfoEnabled() ) { getLog().info( message ); } } private void logWarning( CharSequence message ) { if ( getLog().isWarnEnabled() ) { getLog().warn( message ); } } private void logDebug( CharSequence message ) { if ( getLog().isDebugEnabled() ) { getLog().debug( message ); } } private void logDebugInfo( CharSequence message ) { if ( verbose ) { logInfo( message ); } else { logDebug( message ); } } private abstract class AbstractStatusHandler implements ISVNStatusHandler { private String repositoryRoot; private String repositoryPath; private long maximumRevisionNumber; private long minimumRevisionNumber; private long maximumCommittedRevisionNumber; private final Set localStatusTypes; private final Set remoteStatusTypes; private AbstractStatusHandler() { repositoryRoot = ""; repositoryPath = ""; maximumRevisionNumber = Long.MIN_VALUE; minimumRevisionNumber = Long.MAX_VALUE; maximumCommittedRevisionNumber = Long.MIN_VALUE; localStatusTypes = new HashSet(); remoteStatusTypes = new HashSet(); } protected void setRepositoryProperties( SVNEntry svnEntry ) { repositoryRoot = svnEntry == null ? "" : svnEntry.getRepositoryRoot(); repositoryPath = svnEntry == null || svnEntry.getURL() == null ? "" : svnEntry.getURL().substring( repositoryRoot.length() ); if ( repositoryPath.startsWith( "/" ) ) { repositoryPath = repositoryPath.substring( 1 ); } } protected void appendStatus( SVNStatus status ) { long revisionNumber = status.getRevision().getNumber(); if ( SVNRevision.isValidRevisionNumber( revisionNumber ) ) { maximumRevisionNumber = Math.max( maximumRevisionNumber, revisionNumber ); if ( revisionNumber != 0 ) { minimumRevisionNumber = Math.min( minimumRevisionNumber, revisionNumber ); } } long committedRevisionNumber = status.getCommittedRevision().getNumber(); if ( SVNRevision.isValidRevisionNumber( committedRevisionNumber ) ) { maximumCommittedRevisionNumber = Math.max( maximumCommittedRevisionNumber, committedRevisionNumber ); } SVNStatusType contentsStatusType = status.getContentsStatus(); localStatusTypes.add( contentsStatusType ); SVNStatusType propertiesStatusType = status.getPropertiesStatus(); localStatusTypes.add( propertiesStatusType ); SVNStatusType remoteContentsStatusType = status.getRemoteContentsStatus(); remoteStatusTypes.add( remoteContentsStatusType ); SVNStatusType remotePropertiesStatusType = status.getRemotePropertiesStatus(); remoteStatusTypes.add( remotePropertiesStatusType ); boolean entryOutOfDate = !SVNStatusType.STATUS_NONE.equals( remoteContentsStatusType ) || !SVNStatusType.STATUS_NONE.equals( remotePropertiesStatusType ); StringBuilder buffer = new StringBuilder(); buffer.append( " " ); buffer.append( contentsStatusType.getCode() ).append( propertiesStatusType.getCode() ); buffer.append( entryOutOfDate ? '*' : ' ' ); buffer.append( ' ' ).append( String.format( "%6d", revisionNumber ) ); buffer.append( ' ' ).append( String.format( "%6d", committedRevisionNumber ) ); buffer.append( ' ' ).append( status.getFile() ); logDebugInfo( buffer.toString() ); } public String getRepositoryRoot() { return repositoryRoot; } public String getRepositoryPath() { return repositoryPath; } public long getMaximumRevisionNumber() { return maximumRevisionNumber == Long.MIN_VALUE ? -1L : maximumRevisionNumber; } public long getMinimumRevisionNumber() { return minimumRevisionNumber == Long.MAX_VALUE ? -1L : minimumRevisionNumber; } public boolean isMixedRevisions() { return getMaximumRevisionNumber() > 0L && getMinimumRevisionNumber() > 0L && getMaximumRevisionNumber() != getMinimumRevisionNumber(); } public long getMaximumCommittedRevisionNumber() { return maximumCommittedRevisionNumber == Long.MIN_VALUE ? -1L : maximumCommittedRevisionNumber; } public Set getLocalStatusTypes() { return new HashSet( localStatusTypes ); } public Set getRemoteStatusTypes() { return new HashSet( remoteStatusTypes ); } } private final class VersionedEntryStatusHandler extends AbstractStatusHandler { private VersionedEntryStatusHandler() { } public void handleStatus( SVNStatus status ) { appendStatus( status ); } } private final class SpecialEntryStatusHandler extends AbstractStatusHandler { private final String entryName; private SpecialEntryStatusHandler( String entryName ) { if ( entryName == null ) { throw new IllegalArgumentException( "entryName is null" ); } this.entryName = entryName; } public void handleStatus( SVNStatus status ) { SVNEntry svnEntry = status.getEntry(); if ( svnEntry != null && entryName.equals( svnEntry.getName() ) ) { setRepositoryProperties( svnEntry ); appendStatus( status ); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy