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