blob: 49fc458d78c04ab01412531d747dc99372410e2e [file] [log] [blame]
package org.apache.maven.scm.provider.jazz.command.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.scm.ChangeFile;
import org.apache.maven.scm.ChangeSet;
import org.apache.maven.scm.ScmFileStatus;
import org.apache.maven.scm.log.ScmLogger;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.jazz.command.consumer.AbstractRepositoryConsumer;
import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* Consume the output of the scm command for the "list changesets" operation.
* <p/>
* This parses the contents of the output and uses it to fill in the remaining
* information in the <code>entries</code> list.
*
* @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
*/
public class JazzListChangesetConsumer
extends AbstractRepositoryConsumer
{
//Change sets:
// (1589) ---$ Deb "[maven-release-plugin] prepare for next development iteration"
// Component: (1158) "GPDB"
// Modified: Feb 25, 2012 10:15 PM (Yesterday)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
// (1585) ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
// Component: (1158) "GPDB"
// Modified: Feb 25, 2012 10:13 PM (Yesterday)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
// (1584) ---$ Deb "This is my first changeset (2)"
// Component: (1158) "GPDB"
// Modified: Feb 25, 2012 10:13 PM (Yesterday)
// (1583) ---$ Deb "This is my first changeset (1)"
// Component: (1158) "GPDB"
// Modified: Feb 25, 2012 10:13 PM (Yesterday)
// (1323) ---$ Deb <No comment>
// Component: (1158) "GPDB"
// Modified: Feb 24, 2012 11:04 PM (Last Week)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
// (1319) ---$ Deb <No comment>
// Component: (1158) "GPDB"
// Modified: Feb 24, 2012 11:03 PM (Last Week)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
//
// NOTE: If the change sets originate on the current date, the date is not
// displayed, only the time is.
// EG:
//Change sets:
// (1809) ---$ Deb "[maven-release-plugin] prepare for next development iteration"
// Component: (1158) "GPDB"
// Modified: 6:20 PM (5 minutes ago)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
// (1801) ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.26"
// Component: (1158) "GPDB"
// Modified: 6:18 PM (10 minutes ago)
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// (1799) ---$ Deb <No comment>
// Component: (1158) "GPDB"
// Modified: 6:18 PM (10 minutes ago)
// Changes:
// ---c- (1165) \GPDB\pom.xml
// (1764) ---$ Deb <No comment>
// Component: (1158) "GPDB"
// Modified: Mar 1, 2012 2:34 PM
// Changes:
// ---c- (1165) \GPDB\pom.xml
// State Machine Definitions
private static final int STATE_CHANGE_SETS = 0;
private static final int STATE_CHANGE_SET = 1;
private static final int STATE_COMPONENT = 2;
private static final int STATE_MODIFIED = 3;
private static final int STATE_CHANGES = 4;
// Header definitions.
private static final String HEADER_CHANGE_SETS = "Change sets:";
private static final String HEADER_CHANGE_SET = "(";
private static final String HEADER_COMPONENT = "Component:";
private static final String HEADER_MODIFIED = "Modified:";
private static final String HEADER_CHANGES = "Changes:";
private static final String JAZZ_TIMESTAMP_PATTERN = "MMM d, yyyy h:mm a";
// Actually: DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.SHORT );
private static final String JAZZ_TIMESTAMP_PATTERN_TIME = "h:mm a";
// Only seen when the data = today. Only the time is displayed.
// (1589) ---$ Deb "[maven-release-plugin] prepare for next development iteration"
// (1585) ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
private static final String CHANGESET_PATTERN = "\\((\\d+)\\) (....) (\\w+) (.*)";
/**
* @see #CHANGESET_PATTERN
*/
private RE changeSetRegExp;
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
private static final String CHANGES_PATTERN = "(.....) \\((\\d+)\\) (.*)";
/**
* @see #CHANGES_PATTERN
*/
private RE changesRegExp;
private List<ChangeSet> entries;
private final String userDateFormat;
// This is incremented at the beginning of every change set line. So we start at -1 (to get zero on first processing)
private int currentChangeSetIndex = -1;
private int currentState = STATE_CHANGE_SETS;
/**
* Constructor for our "scm list changeset" consumer.
*
* @param repo The JazzScmProviderRepository being used.
* @param logger The ScmLogger to use.
* @param entries The List of ChangeSet entries that we will populate.
*/
public JazzListChangesetConsumer( ScmProviderRepository repo, ScmLogger logger, List<ChangeSet> entries,
String userDateFormat )
{
super( repo, logger );
this.entries = entries;
this.userDateFormat = userDateFormat;
try
{
changeSetRegExp = new RE( CHANGESET_PATTERN );
changesRegExp = new RE( CHANGES_PATTERN );
}
catch ( RESyntaxException ex )
{
throw new RuntimeException(
"INTERNAL ERROR: Could not create regexp to parse jazz scm history output. This shouldn't happen. Something is probably wrong with the oro installation.",
ex );
}
}
/**
* Process one line of output from the execution of the "scm list changeset" command.
*
* @param line The line of output from the external command that has been pumped to us.
* @see org.codehaus.plexus.util.cli.StreamConsumer#consumeLine(java.lang.String)
*/
public void consumeLine( String line )
{
super.consumeLine( line );
// Process the "Change sets:" line - do nothing
if ( line.trim().startsWith( HEADER_CHANGE_SETS ) )
{
currentState = STATE_CHANGE_SETS;
}
else
{
if ( line.trim().startsWith( HEADER_CHANGE_SET ) )
{
currentState = STATE_CHANGE_SET;
}
else
{
if ( line.trim().startsWith( HEADER_COMPONENT ) )
{
currentState = STATE_COMPONENT;
}
else
{
if ( line.trim().startsWith( HEADER_MODIFIED ) )
{
currentState = STATE_MODIFIED;
}
else
{
if ( line.trim().startsWith( HEADER_CHANGES ) )
{
// Note: processChangesLine() will also be passed the "Changes:" line
// So, it needs to be able to deal with that.
// Changes:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
currentState = STATE_CHANGES;
}
}
}
}
}
switch ( currentState )
{
case STATE_CHANGE_SETS:
// Nothing to do.
break;
case STATE_CHANGE_SET:
processChangeSetLine( line );
break;
case STATE_COMPONENT:
// Nothing to do. Not used (Yet?)
break;
case STATE_MODIFIED:
processModifiedLine( line );
break;
case STATE_CHANGES:
processChangesLine( line );
break;
}
}
private void processChangeSetLine( String line )
{
// Process the headerless change set line - starts with a '(', eg:
// (1589) ---$ Deb "[maven-release-plugin] prepare for next development iteration"
// (1585) ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
if ( changeSetRegExp.match( line ) )
{
// This is the only place this gets incremented.
// It starts at -1, and on first execution is incremented to 0 - which is correct.
currentChangeSetIndex++;
ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
// Init the file of files, so it is not null, but it can be empty!
List<ChangeFile> files = new ArrayList<ChangeFile>();
currentChangeSet.setFiles( files );
String changesetAlias = changeSetRegExp.getParen( 1 );
String changeFlags = changeSetRegExp.getParen( 2 ); // Not used.
String author = changeSetRegExp.getParen( 3 );
String comment = changeSetRegExp.getParen( 4 );
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( " Parsing ChangeSet Line : " + line );
getLogger().debug( " changesetAlias : " + changesetAlias );
getLogger().debug( " changeFlags : " + changeFlags );
getLogger().debug( " author : " + author );
getLogger().debug( " comment : " + comment );
}
// Sanity check.
if ( currentChangeSet.getRevision() != null && !currentChangeSet.getRevision().equals( changesetAlias ) )
{
getLogger().warn( "Warning! The indexes appear to be out of sequence! " +
"For currentChangeSetIndex = " + currentChangeSetIndex + ", we got '" +
changesetAlias + "' and not '" + currentChangeSet.getRevision()
+ "' as expected." );
}
comment = stripDelimiters( comment );
currentChangeSet.setAuthor( author );
currentChangeSet.setComment( comment );
}
}
private void processModifiedLine( String line )
{
// Process the "Modified: ..." line, eg:
// Modified: Feb 25, 2012 10:15 PM (Yesterday)
// Modified: Feb 25, 2012 10:13 PM (Yesterday)
// Modified: Feb 24, 2012 11:03 PM (Last Week)
// Modified: Mar 1, 2012 2:34 PM
// Modified: 6:20 PM (5 minutes ago)
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( " Parsing Modified Line : " + line );
}
int colonPos = line.indexOf( ":" );
int parenPos = line.indexOf( "(" );
String date = null;
if ( colonPos != -1 && parenPos != -1 )
{
date = line.substring( colonPos + 2, parenPos - 1 );
}
else
{
if ( colonPos != -1 && parenPos == -1 )
{
// No trailing bracket
date = line.substring( colonPos + 2 );
}
}
if ( date != null )
{
Date changesetDate = null;
changesetDate = parseDate( date.toString(), userDateFormat, JAZZ_TIMESTAMP_PATTERN );
if ( changesetDate == null )
{
// changesetDate will be null when the date is not given, it only has just the time. The date is today.
changesetDate = parseDate( date.toString(), userDateFormat, JAZZ_TIMESTAMP_PATTERN_TIME );
// Get today's time/date. Used to get the date.
Calendar today = Calendar.getInstance();
// Get a working one.
Calendar changesetCal = Calendar.getInstance();
// Set the date/time. Used to set the time.
changesetCal.setTimeInMillis( changesetDate.getTime() );
// Now set the date (today).
changesetCal.set( today.get( Calendar.YEAR ), today.get( Calendar.MONTH ),
today.get( Calendar.DAY_OF_MONTH ) );
// Now get the date of the combined results.
changesetDate = changesetCal.getTime();
}
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( " date : " + date );
getLogger().debug( " changesetDate : " + changesetDate );
}
ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
currentChangeSet.setDate( changesetDate );
}
}
private void processChangesLine( String line )
{
// Process the changes line, eg:
// ---c- (1170) \GPDB\GPDBEAR\pom.xml
// ---c- (1171) \GPDB\GPDBResources\pom.xml
// ---c- (1167) \GPDB\GPDBWeb\pom.xml
// ---c- (1165) \GPDB\pom.xml
if ( changesRegExp.match( line ) )
{
ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
String changeFlags = changesRegExp.getParen( 1 ); // Not used.
String fileAlias = changesRegExp.getParen( 2 );
String file = changesRegExp.getParen( 3 );
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( " Parsing Changes Line : " + line );
getLogger().debug(
" changeFlags : " + changeFlags + " Translated to : " + parseFileChangeState( changeFlags ) );
getLogger().debug( " filetAlias : " + fileAlias );
getLogger().debug( " file : " + file );
}
ChangeFile changeFile = new ChangeFile( file );
ScmFileStatus status = parseFileChangeState( changeFlags );
changeFile.setAction( status );
currentChangeSet.getFiles().add( changeFile );
}
}
/**
* String the leading/trailing ", < and > from the text.
*
* @param text The text to process.
* @return The striped text.
*/
protected String stripDelimiters( String text )
{
if ( text == null )
{
return null;
}
String workingText = text;
if ( workingText.startsWith( "\"" ) || workingText.startsWith( "<" ) )
{
workingText = workingText.substring( 1 );
}
if ( workingText.endsWith( "\"" ) || workingText.endsWith( ">" ) )
{
workingText = workingText.substring( 0, workingText.length() - 1 );
}
return workingText;
}
/**
* Parse the change state file flags from Jazz and map them to the maven SCM ones.
* <p/>
* "----" Character positions 0-3.
* <p/>
* [0] is '*' or '-' Indicates that this is the current change set ('*') or not ('-'). STATE_CHANGESET_CURRENT
* [1] is '!' or '-' Indicates a Potential Conflict ('!') or not ('-'). STATE_POTENTIAL_CONFLICT
* [2] is '#' or '-' Indicates a Conflict ('#') or not ('-'). STATE_CONFLICT
* [3] is '@' or '$' Indicates whether the changeset is active ('@') or not ('$'). STATE_CHANGESET_ACTIVE
*
* @param state The 5 character long state string
* @return The ScmFileStatus value.
*/
private ScmFileStatus parseChangeSetChangeState( String state )
{
if ( state.length() != 4 )
{
throw new IllegalArgumentException( "Change State string must be 4 chars long!" );
}
// This is not used, but is here for potential future usage and for documentation purposes.
return ScmFileStatus.UNKNOWN;
}
/**
* Parse the change state file flags from Jazz and map them to the maven SCM ones.
* <p/>
* "-----" Character positions 0-4. The default is '-'.
* <p/>
* [0] is '-' or '!' Indicates a Potential Conflict. STATE_POTENTIAL_CONFLICT
* [1] is '-' or '#' Indicates a Conflict. STATE_CONFLICT
* [2] is '-' or 'a' Indicates an addition. STATE_ADD
* or 'd' Indicates a deletion. STATE_DELETE
* or 'm' Indicates a move. STATE_MOVE
* [3] is '-' or 'c' Indicates a content change. STATE_CONTENT_CHANGE
* [4] is '-' or 'p' Indicates a property change. STATE_PROPERTY_CHANGE
* <p/>
* NOTE: [3] and [4] can only be set it [2] is NOT 'a' or 'd'.
*
* @param state The 5 character long state string
* @return The SCMxxx value.
*/
private ScmFileStatus parseFileChangeState( String state )
{
if ( state.length() != 5 )
{
throw new IllegalArgumentException( "Change State string must be 5 chars long!" );
}
// NOTE: We have an impedance mismatch here. The Jazz file change flags represent
// many different states. However, we can only return *ONE* ScmFileStatus value,
// so we need to be careful as to the precedence that we give to them.
ScmFileStatus status = ScmFileStatus.UNKNOWN; // Probably not a valid initial default value.
// [0] is '-' or '!' Indicates a Potential Conflict. STATE_POTENTIAL_CONFLICT
if ( state.charAt( 0 ) == '!' )
{
status = ScmFileStatus.CONFLICT;
}
// [1] is '-' or '#' Indicates a Conflict. STATE_CONFLICT
if ( state.charAt( 1 ) == '#' )
{
status = ScmFileStatus.CONFLICT;
}
// [2] is '-' or 'a' Indicates an addition. STATE_ADD
// or 'd' Indicates a deletion. STATE_DELETE
// or 'm' Indicates a move. STATE_MOVE
if ( state.charAt( 2 ) == 'a' )
{
status = ScmFileStatus.ADDED;
}
else
{
if ( state.charAt( 2 ) == 'd' )
{
status = ScmFileStatus.DELETED;
}
else
{
if ( state.charAt( 2 ) == 'm' )
{
status = ScmFileStatus.RENAMED; // Has been renamed or moved.
}
// [3] is '-' or 'c' Indicates a content change. STATE_CONTENT_CHANGE
if ( state.charAt( 3 ) == 'c' )
{
status = ScmFileStatus.MODIFIED; // The file has been modified in the working tree.
}
// [4] is '-' or 'p' Indicates a property change. STATE_PROPERTY_CHANGE
if ( state.charAt( 4 ) == 'p' )
{
status =
ScmFileStatus.MODIFIED; // ScmFileStatus has no concept of property or meta data changes.
}
}
}
return status;
}
}