View Javadoc

1   package org.apache.maven.scm.provider.svn.svnexe.command.changelog;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.scm.ChangeFile;
23  import org.apache.maven.scm.ChangeSet;
24  import org.apache.maven.scm.ScmFileStatus;
25  import org.apache.maven.scm.log.ScmLogger;
26  import org.apache.maven.scm.provider.svn.SvnChangeSet;
27  import org.apache.maven.scm.util.AbstractConsumer;
28  import org.apache.regexp.RE;
29  
30  import java.util.ArrayList;
31  import java.util.Date;
32  import java.util.List;
33  
34  /**
35   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
36   *
37   */
38  public class SvnChangeLogConsumer
39      extends AbstractConsumer
40  {
41      /**
42       * Date formatter for svn timestamp (after a little massaging)
43       */
44      private static final String SVN_TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss zzzzzzzzz";
45  
46      /**
47       * State machine constant: expecting header
48       */
49      private static final int GET_HEADER = 1;
50  
51      /**
52       * State machine constant: expecting file information
53       */
54      private static final int GET_FILE = 2;
55  
56      /**
57       * State machine constant: expecting comments
58       */
59      private static final int GET_COMMENT = 3;
60  
61      /**
62       * There is always action and affected path; when copying/moving, recognize also original path and revision
63       */
64      private static final RE FILE_PATTERN = new RE( "^\\s\\s\\s([:upper:])\\s(.+)$" );
65  
66      /**
67       * This matches the 'original file info' part of the complete file line.
68       * Note the use of [:alpha:] instead of literal 'from' - this is meant to allow non-English localizations.
69       */
70      private static final RE ORIG_FILE_PATTERN = new RE( "\\([:alpha:]+ (.+):(\\d+)\\)" );
71  
72      /**
73       * The file section ends with a blank line
74       */
75      private static final String FILE_END_TOKEN = "";
76  
77      /**
78       * The comment section ends with a dashed line
79       */
80      private static final String COMMENT_END_TOKEN =
81          "------------------------------------" + "------------------------------------";
82  
83      /**
84       * Current status of the parser
85       */
86      private int status = GET_HEADER;
87  
88      /**
89       * List of change log entries
90       */
91      private List<ChangeSet> entries = new ArrayList<ChangeSet>();
92  
93      /**
94       * The current log entry being processed by the parser
95       */
96      private SvnChangeSet currentChange;
97  
98      /**
99       * 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 }