View Javadoc
1   package org.apache.maven.scm.provider.accurev.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 java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.maven.scm.ChangeFile;
31  import org.apache.maven.scm.ChangeSet;
32  import org.apache.maven.scm.CommandParameter;
33  import org.apache.maven.scm.CommandParameters;
34  import org.apache.maven.scm.ScmBranch;
35  import org.apache.maven.scm.ScmException;
36  import org.apache.maven.scm.ScmFileSet;
37  import org.apache.maven.scm.ScmResult;
38  import org.apache.maven.scm.ScmRevision;
39  import org.apache.maven.scm.ScmVersion;
40  import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
41  import org.apache.maven.scm.command.changelog.ChangeLogSet;
42  import org.apache.maven.scm.log.ScmLogger;
43  import org.apache.maven.scm.provider.ScmProviderRepository;
44  import org.apache.maven.scm.provider.accurev.AccuRev;
45  import org.apache.maven.scm.provider.accurev.AccuRevCapability;
46  import org.apache.maven.scm.provider.accurev.AccuRevException;
47  import org.apache.maven.scm.provider.accurev.AccuRevScmProviderRepository;
48  import org.apache.maven.scm.provider.accurev.AccuRevVersion;
49  import org.apache.maven.scm.provider.accurev.FileDifference;
50  import org.apache.maven.scm.provider.accurev.Stream;
51  import org.apache.maven.scm.provider.accurev.Transaction;
52  import org.apache.maven.scm.provider.accurev.Transaction.Version;
53  import org.apache.maven.scm.provider.accurev.command.AbstractAccuRevCommand;
54  import org.codehaus.plexus.util.StringUtils;
55  
56  /**
57   * TODO filter results based on project_path Find appropriate start and end transaction ids from parameters. Streams
58   * must be the same. Diff on stream start to end - these are the upstream changes Hist on the stream start+1 to end
59   * remove items from the upstream set if they appear in the history For workspaces diff doesn't work. So we would not
60   * pickup any upstream changes, just the "keep" transactions which is not very useful. Hist on the workspace Then diff /
61   * hist on the basis stream, skipping any transactions that are coming from the workspace.
62   * 
63   * @author ggardner
64   */
65  public class AccuRevChangeLogCommand
66      extends AbstractAccuRevCommand
67  {
68  
69      public AccuRevChangeLogCommand( ScmLogger logger )
70      {
71          super( logger );
72      }
73  
74      @Override
75      protected ScmResult executeAccurevCommand( AccuRevScmProviderRepository repository, ScmFileSet fileSet,
76                                                 CommandParameters parameters )
77          throws ScmException, AccuRevException
78      {
79  
80          // Do we have a supplied branch. If not we default to the URL stream.
81          ScmBranch branch = (ScmBranch) parameters.getScmVersion( CommandParameter.BRANCH, null );
82          AccuRevVersion branchVersion = repository.getAccuRevVersion( branch );
83          String stream = branchVersion.getBasisStream();
84          String fromSpec = branchVersion.getTimeSpec();
85          String toSpec = "highest";
86  
87          // Versions
88          ScmVersion startVersion = parameters.getScmVersion( CommandParameter.START_SCM_VERSION, null );
89          ScmVersion endVersion = parameters.getScmVersion( CommandParameter.END_SCM_VERSION, null );
90  
91          if ( startVersion != null && StringUtils.isNotEmpty( startVersion.getName() ) )
92          {
93              AccuRevVersion fromVersion = repository.getAccuRevVersion( startVersion );
94              // if no end version supplied then use same basis as startVersion
95              AccuRevVersion toVersion =
96                  endVersion == null ? new AccuRevVersion( fromVersion.getBasisStream(), "now" )
97                                  : repository.getAccuRevVersion( endVersion );
98                  
99              if ( !StringUtils.equals( fromVersion.getBasisStream(), toVersion.getBasisStream() ) )
100             {
101                 throw new AccuRevException( "Not able to provide change log between different streams " + fromVersion
102                     + "," + toVersion );
103             }
104 
105             stream = fromVersion.getBasisStream();
106             fromSpec = fromVersion.getTimeSpec();
107             toSpec = toVersion.getTimeSpec();
108 
109         }
110 
111         Date startDate = parameters.getDate( CommandParameter.START_DATE, null );
112         Date endDate = parameters.getDate( CommandParameter.END_DATE, null );
113         int numDays = parameters.getInt( CommandParameter.NUM_DAYS, 0 );
114 
115         if ( numDays > 0 )
116         {
117             if ( ( startDate != null || endDate != null ) )
118             {
119                 throw new ScmException( "Start or end date cannot be set if num days is set." );
120             }
121             // Last x days.
122             int day = 24 * 60 * 60 * 1000;
123             startDate = new Date( System.currentTimeMillis() - (long) numDays * day );
124             endDate = new Date( System.currentTimeMillis() + day );
125         }
126 
127         if ( endDate != null && startDate == null )
128         {
129             throw new ScmException( "The end date is set but the start date isn't." );
130         }
131 
132         // Date parameters override transaction ids in versions
133         if ( startDate != null )
134         {
135             fromSpec = AccuRevScmProviderRepository.formatTimeSpec( startDate );
136         }
137         else if ( fromSpec == null )
138         {
139             fromSpec = "1";
140         }
141 
142         // Convert the fromSpec to both a date AND a transaction id by looking up
143         // the nearest transaction in the depot.
144         Transaction fromTransaction = getDepotTransaction( repository, stream, fromSpec );
145 
146         long fromTranId = 1;
147         if ( fromTransaction != null )
148         {
149             // This tran id is less than or equal to the date/tranid we requested.
150             fromTranId = fromTransaction.getTranId();
151             if ( startDate == null )
152             {
153                 startDate = fromTransaction.getWhen();
154             }
155         }
156 
157         if ( endDate != null )
158         {
159             toSpec = AccuRevScmProviderRepository.formatTimeSpec( endDate );
160         }
161         else if ( toSpec == null )
162         {
163             toSpec = "highest";
164         }
165 
166         Transaction toTransaction = getDepotTransaction( repository, stream, toSpec );
167         long toTranId = 1;
168         if ( toTransaction != null )
169         {
170             toTranId = toTransaction.getTranId();
171             if ( endDate == null )
172             {
173                 endDate = toTransaction.getWhen();
174             }
175         }
176         startVersion = new ScmRevision( repository.getRevision( stream, fromTranId ) );
177         endVersion = new ScmRevision( repository.getRevision( stream, toTranId ) );
178 
179         //TODO Split this method in two here. above to convert params to start and end (stream,tranid,date) and test independantly
180         
181         List<Transaction> streamHistory = Collections.emptyList();
182         List<Transaction> workspaceHistory = Collections.emptyList();
183         List<FileDifference> streamDifferences = Collections.emptyList();
184 
185         StringBuilder errorMessage = new StringBuilder();
186 
187         AccuRev accurev = repository.getAccuRev();
188 
189         Stream changelogStream = accurev.showStream( stream );
190         if ( changelogStream == null )
191         {
192             errorMessage.append( "Unknown accurev stream -" ).append( stream ).append( "." );
193         }
194         else
195         {
196 
197             String message =
198                 "Changelog on stream " + stream + "(" + changelogStream.getStreamType() + ") from " + fromTranId + " ("
199                     + startDate + "), to " + toTranId + " (" + endDate + ")";
200 
201             if ( startDate != null && startDate.after( endDate ) || fromTranId >= toTranId )
202             {
203                 getLogger().warn( "Skipping out of range " + message );
204             }
205             else
206             {
207 
208                 getLogger().info( message );
209 
210                 // In 4.7.2 and higher we have a diff command that will list all the file differences in a stream
211                 // and thus can be used to detect upstream changes
212                 // Unfortunately diff -v -V -t does not work in workspaces.
213                 Stream diffStream = changelogStream;
214                 if ( changelogStream.isWorkspace() )
215                 {
216 
217                     workspaceHistory =
218                         accurev.history( stream, Long.toString( fromTranId + 1 ), Long.toString( toTranId ), 0, false,
219                                          false );
220 
221                     if ( workspaceHistory == null )
222                     {
223                         errorMessage.append( "history on workspace " + stream + " from " + fromTranId + 1 + " to "
224                             + toTranId + " failed." );
225 
226                     }
227 
228                     // do the diff/hist on the basis stream instead.
229                     stream = changelogStream.getBasis();
230                     diffStream = accurev.showStream( stream );
231 
232                 }
233 
234                 if ( AccuRevCapability.DIFF_BETWEEN_STREAMS.isSupported( accurev.getClientVersion() ) )
235                 {
236                     if ( startDate.before( diffStream.getStartDate() ) )
237                     {
238                         getLogger().warn( "Skipping diff of " + stream + " due to start date out of range" );
239                     }
240                     else
241                     {
242                         streamDifferences =
243                             accurev.diff( stream, Long.toString( fromTranId ), Long.toString( toTranId ) );
244                         if ( streamDifferences == null )
245                         {
246                             errorMessage.append( "Diff " + stream + "- " + fromTranId + " to " + toTranId + "failed." );
247                         }
248                     }
249                 }
250 
251                 // History needs to start from the transaction after our starting transaction
252 
253                 streamHistory =
254                     accurev.history( stream, Long.toString( fromTranId + 1 ), Long.toString( toTranId ), 0, false,
255                                      false );
256                 if ( streamHistory == null )
257                 {
258                     errorMessage.append( "history on stream " + stream + " from " + fromTranId + 1 + " to " + toTranId
259                         + " failed." );
260                 }
261 
262             }
263         }
264 
265         String errorString = errorMessage.toString();
266         if ( StringUtils.isBlank( errorString ) )
267         {
268             ChangeLogSet changeLog =
269                 getChangeLog( changelogStream, streamDifferences, streamHistory, workspaceHistory, startDate, endDate );
270 
271             changeLog.setEndVersion( endVersion );
272             changeLog.setStartVersion( startVersion );
273 
274             return new ChangeLogScmResult( accurev.getCommandLines(), changeLog );
275         }
276         else
277         {
278             return new ChangeLogScmResult( accurev.getCommandLines(), "AccuRev errors: " + errorMessage,
279                                            accurev.getErrorOutput(), false );
280         }
281 
282     }
283 
284     private Transaction getDepotTransaction( AccuRevScmProviderRepository repo, String stream, String tranSpec )
285         throws AccuRevException
286     {
287         return repo.getDepotTransaction( stream, tranSpec );
288     }
289 
290     private ChangeLogSet getChangeLog( Stream stream, List<FileDifference> streamDifferences,
291                                        List<Transaction> streamHistory, List<Transaction> workspaceHistory,
292                                        Date startDate, Date endDate )
293     {
294 
295         // Collect all the "to" versions from the streamDifferences into a Map by element id
296         // If that version is seen in the promote/keep history then we move it from the map
297         // At the end we create a pseudo ChangeSet for any remaining entries in the map as
298         // representing "upstream changes"
299         Map<Long, FileDifference> differencesMap = new HashMap<Long, FileDifference>();
300         for ( FileDifference fileDifference : streamDifferences )
301         {
302             differencesMap.put( fileDifference.getElementId(), fileDifference );
303         }
304 
305         List<Transaction> mergedHistory = new ArrayList<Transaction>( streamHistory );
306         // will never match a version
307         String streamPrefix = "/";
308 
309         mergedHistory.addAll( workspaceHistory );
310         streamPrefix = stream.getId() + "/";
311 
312         List<ChangeSet> entries = new ArrayList<ChangeSet>( streamHistory.size() );
313         for ( Transaction t : mergedHistory )
314         {
315             if ( ( startDate != null && t.getWhen().before( startDate ) )
316                 || ( endDate != null && t.getWhen().after( endDate ) ) )
317             {
318                 // This is possible if dates and transactions are mixed in the time spec.
319                 continue;
320             }
321 
322             // Needed to make Tck test pass against accurev > 4.7.2 - the changelog only expects to deal with
323             // files. Stream changes and cross links are important entries in the changelog.
324             // However we should only see mkstream once and it is irrelevant given we are interrogating
325             // the history of this stream.
326             if ( "mkstream".equals( t.getTranType() ) )
327             {
328                 continue;
329             }
330 
331             Collection<Version> versions = t.getVersions();
332             List<ChangeFile> files = new ArrayList<ChangeFile>( versions.size() );
333 
334             for ( Version v : versions )
335             {
336 
337                 // Remove diff representing this promote
338                 FileDifference difference = differencesMap.get( v.getElementId() );
339                 // TODO: how are defuncts shown in the version history?
340                 if ( difference != null )
341                 {
342                     String newVersionSpec = difference.getNewVersionSpec();
343                     if ( newVersionSpec != null && newVersionSpec.equals( v.getRealSpec() ) )
344                     {
345                         if ( getLogger().isDebugEnabled() )
346                         {
347                             getLogger().debug( "Removing difference for " + v );
348                         }
349                         differencesMap.remove( v.getElementId() );
350                     }
351                 }
352 
353                 // Add this file, unless the virtual version indicates this is the basis stream, and the real
354                 // version came from our workspace stream (ie, this transaction is a promote from the workspace
355                 // to its basis stream, and is therefore NOT a change
356                 if ( v.getRealSpec().startsWith( streamPrefix ) && !v.getVirtualSpec().startsWith( streamPrefix ) )
357                 {
358                     if ( getLogger().isDebugEnabled() )
359                     {
360                         getLogger().debug( "Skipping workspace to basis stream promote " + v );
361                     }
362                 }
363                 else
364                 {
365                     ChangeFile f =
366                         new ChangeFile( v.getElementName(), v.getVirtualSpec() + " (" + v.getRealSpec() + ")" );
367                     files.add( f );
368                 }
369 
370             }
371 
372             if ( versions.isEmpty() || !files.isEmpty() )
373             {
374                 ChangeSet changeSet = new ChangeSet( t.getWhen(), t.getComment(), t.getAuthor(), files );
375 
376                 entries.add( changeSet );
377             }
378             else
379             {
380                 if ( getLogger().isDebugEnabled() )
381                 {
382                     getLogger().debug( "All versions removed for " + t );
383                 }
384             }
385 
386         }
387 
388         // Anything left in the differencesMap represents a change from a higher stream
389         // We don't have details on who or where these came from, but it is important to
390         // detect these for CI tools like Continuum
391         if ( !differencesMap.isEmpty() )
392         {
393             List<ChangeFile> upstreamFiles = new ArrayList<ChangeFile>();
394             for ( FileDifference difference : differencesMap.values() )
395             {
396                 if ( difference.getNewVersionSpec() != null )
397                 {
398                     upstreamFiles.add( new ChangeFile( difference.getNewFile().getPath(),
399                                                        difference.getNewVersionSpec() ) );
400                 }
401                 else
402                 {
403                     // difference is a deletion
404                     upstreamFiles.add( new ChangeFile( difference.getOldFile().getPath(), null ) );
405                 }
406             }
407             entries.add( new ChangeSet( endDate, "Upstream changes", "various", upstreamFiles ) );
408         }
409 
410         return new ChangeLogSet( entries, startDate, endDate );
411     }
412 
413     public ChangeLogScmResult changelog( ScmProviderRepository repo, ScmFileSet testFileSet, CommandParameters params )
414         throws ScmException
415     {
416         return (ChangeLogScmResult) execute( repo, testFileSet, params );
417     }
418 
419 }