001 package org.apache.maven.scm.provider.svn.svnexe.command.changelog; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.maven.scm.ChangeFile; 023 import org.apache.maven.scm.ChangeSet; 024 import org.apache.maven.scm.ScmFileStatus; 025 import org.apache.maven.scm.log.ScmLogger; 026 import org.apache.maven.scm.provider.svn.SvnChangeSet; 027 import org.apache.maven.scm.util.AbstractConsumer; 028 import org.apache.regexp.RE; 029 030 import java.util.ArrayList; 031 import java.util.Date; 032 import java.util.List; 033 034 /** 035 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> 036 * @version $Id: SvnChangeLogConsumer.java 1306862 2012-03-29 13:42:39Z olamy $ 037 */ 038 public class SvnChangeLogConsumer 039 extends AbstractConsumer 040 { 041 /** 042 * Date formatter for svn timestamp (after a little massaging) 043 */ 044 private static final String SVN_TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss zzzzzzzzz"; 045 046 /** 047 * State machine constant: expecting header 048 */ 049 private static final int GET_HEADER = 1; 050 051 /** 052 * State machine constant: expecting file information 053 */ 054 private static final int GET_FILE = 2; 055 056 /** 057 * State machine constant: expecting comments 058 */ 059 private static final int GET_COMMENT = 3; 060 061 /** 062 * There is always action and affected path; when copying/moving, recognize also original path and revision 063 */ 064 private static final RE FILE_PATTERN = new RE( "^\\s\\s\\s([:upper:])\\s(.+)$" ); 065 066 /** 067 * This matches the 'original file info' part of the complete file line. 068 * Note the use of [:alpha:] instead of literal 'from' - this is meant to allow non-English localizations. 069 */ 070 private static final RE ORIG_FILE_PATTERN = new RE( "\\([:alpha:]+ (.+):(\\d+)\\)" ); 071 072 /** 073 * The file section ends with a blank line 074 */ 075 private static final String FILE_END_TOKEN = ""; 076 077 /** 078 * The comment section ends with a dashed line 079 */ 080 private static final String COMMENT_END_TOKEN = 081 "------------------------------------" + "------------------------------------"; 082 083 /** 084 * Current status of the parser 085 */ 086 private int status = GET_HEADER; 087 088 /** 089 * List of change log entries 090 */ 091 private List<ChangeSet> entries = new ArrayList<ChangeSet>(); 092 093 /** 094 * The current log entry being processed by the parser 095 */ 096 private SvnChangeSet currentChange; 097 098 /** 099 * The current revision of the entry being processed by the parser 100 */ 101 private String currentRevision; 102 103 /** 104 * The current comment of the entry being processed by the parser 105 */ 106 private StringBuilder currentComment; 107 108 /** 109 * The regular expression used to match header lines 110 */ 111 private static final RE HEADER_REG_EXP = new RE( "^(.+) \\| (.+) \\| (.+) \\|.*$" ); 112 113 private static final int REVISION_GROUP = 1; 114 115 private static final int AUTHOR_GROUP = 2; 116 117 private static final int DATE_GROUP = 3; 118 119 private static final RE REVISION_REG_EXP1 = new RE( "rev (\\d+):" ); 120 121 private static final RE REVISION_REG_EXP2 = new RE( "r(\\d+)" ); 122 123 private static final RE DATE_REG_EXP = new RE( "(\\d+-\\d+-\\d+ " + // date 2002-08-24 124 "\\d+:\\d+:\\d+) " + // time 16:01:00 125 "([\\-+])(\\d\\d)(\\d\\d)" ); // gmt offset -0400);) 126 127 private final String userDateFormat; 128 129 /** 130 * Default constructor. 131 */ 132 public SvnChangeLogConsumer( ScmLogger logger, String userDateFormat ) 133 { 134 super( logger ); 135 136 this.userDateFormat = userDateFormat; 137 } 138 139 public List<ChangeSet> getModifications() 140 { 141 return entries; 142 } 143 144 // ---------------------------------------------------------------------- 145 // StreamConsumer Implementation 146 // ---------------------------------------------------------------------- 147 148 /** 149 * {@inheritDoc} 150 */ 151 public void consumeLine( String line ) 152 { 153 if ( getLogger().isDebugEnabled() ) 154 { 155 getLogger().debug( line ); 156 } 157 switch ( status ) 158 { 159 case GET_HEADER: 160 processGetHeader( line ); 161 break; 162 case GET_FILE: 163 processGetFile( line ); 164 break; 165 case GET_COMMENT: 166 processGetComment( line ); 167 break; 168 default: 169 throw new IllegalStateException( "Unknown state: " + status ); 170 } 171 } 172 173 // ---------------------------------------------------------------------- 174 // 175 // ---------------------------------------------------------------------- 176 177 /** 178 * Process the current input line in the GET_HEADER state. The 179 * author, date, and the revision of the entry are gathered. Note, 180 * Subversion does not have per-file revisions, instead, the entire 181 * repository is given a single revision number, which is used for 182 * the revision number of each file. 183 * 184 * @param line A line of text from the svn log output 185 */ 186 private void processGetHeader( String line ) 187 { 188 if ( !HEADER_REG_EXP.match( line ) ) 189 { 190 // The header line is not found. Intentionally do nothing. 191 return; 192 } 193 194 currentRevision = getRevision( HEADER_REG_EXP.getParen( REVISION_GROUP ) ); 195 196 currentChange = new SvnChangeSet(); 197 198 currentChange.setAuthor( HEADER_REG_EXP.getParen( AUTHOR_GROUP ) ); 199 200 currentChange.setDate( getDate( HEADER_REG_EXP.getParen( DATE_GROUP ) ) ); 201 202 currentChange.setRevision( currentRevision ); 203 204 status = GET_FILE; 205 } 206 207 /** 208 * Gets the svn revision, from the svn log revision output. 209 * 210 * @param revisionOutput 211 * @return the svn revision 212 */ 213 private String getRevision( final String revisionOutput ) 214 { 215 if ( REVISION_REG_EXP1.match( revisionOutput ) ) 216 { 217 return REVISION_REG_EXP1.getParen( 1 ); 218 } 219 else if ( REVISION_REG_EXP2.match( revisionOutput ) ) 220 { 221 return REVISION_REG_EXP2.getParen( 1 ); 222 } 223 else 224 { 225 throw new IllegalOutputException( revisionOutput ); 226 } 227 } 228 229 /** 230 * Process the current input line in the GET_FILE state. This state 231 * adds each file entry line to the current change log entry. Note, 232 * the revision number for the entire entry is used for the revision 233 * number of each file. 234 * 235 * @param line A line of text from the svn log output 236 */ 237 private void processGetFile( String line ) 238 { 239 if ( FILE_PATTERN.match( line ) ) 240 { 241 final String fileinfo = FILE_PATTERN.getParen( 2 ); 242 String name = fileinfo; 243 String originalName = null; 244 String originalRev = null; 245 final int n = fileinfo.indexOf( " (" ); 246 if ( n > 1 && fileinfo.endsWith( ")" ) ) 247 { 248 final String origFileInfo = fileinfo.substring( n ); 249 if ( ORIG_FILE_PATTERN.match( origFileInfo ) ) 250 { 251 // if original file is present, we must extract the affected one from the beginning 252 name = fileinfo.substring( 0, n ); 253 originalName = ORIG_FILE_PATTERN.getParen( 1 ); 254 originalRev = ORIG_FILE_PATTERN.getParen( 2 ); 255 } 256 } 257 final String actionStr = FILE_PATTERN.getParen( 1 ); 258 final ScmFileStatus action; 259 if ( "A".equals( actionStr ) ) 260 { 261 //TODO: this may even change to MOVED if we later explore whole changeset and find matching DELETED 262 action = originalRev == null ? ScmFileStatus.ADDED : ScmFileStatus.COPIED; 263 } 264 else if ( "D".equals( actionStr ) ) 265 { 266 action = ScmFileStatus.DELETED; 267 } 268 else if ( "M".equals( actionStr ) ) 269 { 270 action = ScmFileStatus.MODIFIED; 271 } 272 else if ( "R".equals( actionStr ) ) 273 { 274 action = ScmFileStatus.UPDATED; //== REPLACED in svn terms 275 } 276 else 277 { 278 action = ScmFileStatus.UNKNOWN; 279 } 280 System.out.println( actionStr + " : " + name ); 281 final ChangeFile changeFile = new ChangeFile( name, currentRevision ); 282 changeFile.setAction( action ); 283 changeFile.setOriginalName( originalName ); 284 changeFile.setOriginalRevision( originalRev ); 285 currentChange.addFile( changeFile ); 286 287 status = GET_FILE; 288 } 289 else if ( line.equals( FILE_END_TOKEN ) ) 290 { 291 // Create a buffer for the collection of the comment now 292 // that we are leaving the GET_FILE state. 293 currentComment = new StringBuilder(); 294 295 status = GET_COMMENT; 296 } 297 } 298 299 /** 300 * Process the current input line in the GET_COMMENT state. This 301 * state gathers all of the comments that are part of a log entry. 302 * 303 * @param line a line of text from the svn log output 304 */ 305 private void processGetComment( String line ) 306 { 307 if ( line.equals( COMMENT_END_TOKEN ) ) 308 { 309 currentChange.setComment( currentComment.toString() ); 310 311 entries.add( currentChange ); 312 313 status = GET_HEADER; 314 } 315 else 316 { 317 currentComment.append( line ).append( '\n' ); 318 } 319 } 320 321 /** 322 * Converts the date time stamp from the svn output into a date 323 * object. 324 * 325 * @param dateOutput The date output from an svn log command. 326 * @return A date representing the time stamp of the log entry. 327 */ 328 private Date getDate( final String dateOutput ) 329 { 330 if ( !DATE_REG_EXP.match( dateOutput ) ) 331 { 332 throw new IllegalOutputException( dateOutput ); 333 } 334 335 final StringBuilder date = new StringBuilder(); 336 date.append( DATE_REG_EXP.getParen( 1 ) ); 337 date.append( " GMT" ); 338 date.append( DATE_REG_EXP.getParen( 2 ) ); 339 date.append( DATE_REG_EXP.getParen( 3 ) ); 340 date.append( ':' ); 341 date.append( DATE_REG_EXP.getParen( 4 ) ); 342 343 return parseDate( date.toString(), userDateFormat, SVN_TIMESTAMP_PATTERN ); 344 } 345 }