| 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; |
| } |
| } |