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