001 package org.apache.maven.scm.provider.accurev.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 java.util.ArrayList;
023 import java.util.Collection;
024 import java.util.Collections;
025 import java.util.Date;
026 import java.util.HashMap;
027 import java.util.List;
028 import java.util.Map;
029
030 import org.apache.maven.scm.ChangeFile;
031 import org.apache.maven.scm.ChangeSet;
032 import org.apache.maven.scm.CommandParameter;
033 import org.apache.maven.scm.CommandParameters;
034 import org.apache.maven.scm.ScmBranch;
035 import org.apache.maven.scm.ScmException;
036 import org.apache.maven.scm.ScmFileSet;
037 import org.apache.maven.scm.ScmResult;
038 import org.apache.maven.scm.ScmRevision;
039 import org.apache.maven.scm.ScmVersion;
040 import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
041 import org.apache.maven.scm.command.changelog.ChangeLogSet;
042 import org.apache.maven.scm.log.ScmLogger;
043 import org.apache.maven.scm.provider.ScmProviderRepository;
044 import org.apache.maven.scm.provider.accurev.AccuRev;
045 import org.apache.maven.scm.provider.accurev.AccuRevCapability;
046 import org.apache.maven.scm.provider.accurev.AccuRevException;
047 import org.apache.maven.scm.provider.accurev.AccuRevScmProviderRepository;
048 import org.apache.maven.scm.provider.accurev.AccuRevVersion;
049 import org.apache.maven.scm.provider.accurev.FileDifference;
050 import org.apache.maven.scm.provider.accurev.Stream;
051 import org.apache.maven.scm.provider.accurev.Transaction;
052 import org.apache.maven.scm.provider.accurev.Transaction.Version;
053 import org.apache.maven.scm.provider.accurev.command.AbstractAccuRevCommand;
054 import org.codehaus.plexus.util.StringUtils;
055
056 /**
057 * TODO filter results based on project_path Find appropriate start and end transaction ids from parameters. Streams
058 * must be the same. Diff on stream start to end - these are the upstream changes Hist on the stream start+1 to end
059 * remove items from the upstream set if they appear in the history For workspaces diff doesn't work. So we would not
060 * pickup any upstream changes, just the "keep" transactions which is not very useful. Hist on the workspace Then diff /
061 * hist on the basis stream, skipping any transactions that are coming from the workspace.
062 *
063 * @author ggardner
064 */
065 public class AccuRevChangeLogCommand
066 extends AbstractAccuRevCommand
067 {
068
069 public AccuRevChangeLogCommand( ScmLogger logger )
070 {
071
072 super( logger );
073 }
074
075 @Override
076 protected ScmResult executeAccurevCommand( AccuRevScmProviderRepository repository, ScmFileSet fileSet,
077 CommandParameters parameters )
078 throws ScmException, AccuRevException
079 {
080
081 // Do we have a supplied branch. If not we default to the URL stream.
082 ScmBranch branch = (ScmBranch) parameters.getScmVersion( CommandParameter.BRANCH, null );
083 AccuRevVersion branchVersion = repository.getAccuRevVersion( branch );
084 String stream = branchVersion.getBasisStream();
085 String fromSpec = branchVersion.getTimeSpec();
086 String toSpec = "highest";
087
088 // Versions
089 ScmVersion startVersion = parameters.getScmVersion( CommandParameter.START_SCM_VERSION, null );
090 ScmVersion endVersion = parameters.getScmVersion( CommandParameter.END_SCM_VERSION, null );
091
092 if ( startVersion != null && StringUtils.isNotEmpty( startVersion.getName() ) )
093 {
094 AccuRevVersion fromVersion = repository.getAccuRevVersion( startVersion );
095 // if no end version supplied then use same basis as startVersion
096 AccuRevVersion toVersion =
097 endVersion == null ? new AccuRevVersion( fromVersion.getBasisStream(), "now" )
098 : repository.getAccuRevVersion( endVersion );
099
100 if ( !StringUtils.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
292 private ChangeLogSet getChangeLog( Stream stream, List<FileDifference> streamDifferences,
293 List<Transaction> streamHistory, List<Transaction> workspaceHistory,
294 Date startDate, Date endDate )
295 {
296
297 // Collect all the "to" versions from the streamDifferences into a Map by element id
298 // If that version is seen in the promote/keep history then we move it from the map
299 // At the end we create a pseudo ChangeSet for any remaining entries in the map as
300 // representing "upstream changes"
301 Map<Long, FileDifference> differencesMap = new HashMap<Long, FileDifference>();
302 for ( FileDifference fileDifference : streamDifferences )
303 {
304 differencesMap.put( fileDifference.getElementId(), fileDifference );
305 }
306
307 List<Transaction> mergedHistory = new ArrayList<Transaction>( streamHistory );
308 // will never match a version
309 String streamPrefix = "/";
310
311 mergedHistory.addAll( workspaceHistory );
312 streamPrefix = stream.getId() + "/";
313
314 List<ChangeSet> entries = new ArrayList<ChangeSet>( streamHistory.size() );
315 for ( Transaction t : mergedHistory )
316 {
317 if ( ( startDate != null && t.getWhen().before( startDate ) )
318 || ( endDate != null && t.getWhen().after( endDate ) ) )
319 {
320 // This is possible if dates and transactions are mixed in the time spec.
321 continue;
322 }
323
324 // Needed to make Tck test pass against accurev > 4.7.2 - the changelog only expects to deal with
325 // files. Stream changes and cross links are important entries in the changelog.
326 // However we should only see mkstream once and it is irrelevant given we are interrogating
327 // the history of this stream.
328 if ( "mkstream".equals( t.getTranType() ) )
329 {
330 continue;
331 }
332
333 Collection<Version> versions = t.getVersions();
334 List<ChangeFile> files = new ArrayList<ChangeFile>( versions.size() );
335
336 for ( Version v : versions )
337 {
338
339 // Remove diff representing this promote
340 FileDifference difference = differencesMap.get( v.getElementId() );
341 // TODO: how are defuncts shown in the version history?
342 if ( difference != null )
343 {
344 String newVersionSpec = difference.getNewVersionSpec();
345 if ( newVersionSpec != null && newVersionSpec.equals( v.getRealSpec() ) )
346 {
347 if ( getLogger().isDebugEnabled() )
348 {
349 getLogger().debug( "Removing difference for " + v );
350 }
351 differencesMap.remove( v.getElementId() );
352 }
353 }
354
355 // Add this file, unless the virtual version indicates this is the basis stream, and the real
356 // version came from our workspace stream (ie, this transaction is a promote from the workspace
357 // to its basis stream, and is therefore NOT a change
358 if ( v.getRealSpec().startsWith( streamPrefix ) && !v.getVirtualSpec().startsWith( streamPrefix ) )
359 {
360 if ( getLogger().isDebugEnabled() )
361 {
362 getLogger().debug( "Skipping workspace to basis stream promote " + v );
363 }
364 }
365 else
366 {
367 ChangeFile f =
368 new ChangeFile( v.getElementName(), v.getVirtualSpec() + " (" + v.getRealSpec() + ")" );
369 files.add( f );
370 }
371
372 }
373
374 if ( versions.isEmpty() || !files.isEmpty() )
375 {
376 ChangeSet changeSet = new ChangeSet( t.getWhen(), t.getComment(), t.getAuthor(), files );
377
378 entries.add( changeSet );
379 }
380 else
381 {
382 if ( getLogger().isDebugEnabled() )
383 {
384 getLogger().debug( "All versions removed for " + t );
385 }
386 }
387
388 }
389
390 // Anything left in the differencesMap represents a change from a higher stream
391 // We don't have details on who or where these came from, but it is important to
392 // detect these for CI tools like Continuum
393 if ( !differencesMap.isEmpty() )
394 {
395 List<ChangeFile> upstreamFiles = new ArrayList<ChangeFile>();
396 for ( FileDifference difference : differencesMap.values() )
397 {
398 if ( difference.getNewVersionSpec() != null )
399 {
400 upstreamFiles.add( new ChangeFile( difference.getNewFile().getPath(),
401 difference.getNewVersionSpec() ) );
402 }
403 else
404 {
405 // difference is a deletion
406 upstreamFiles.add( new ChangeFile( difference.getOldFile().getPath(), null ) );
407 }
408 }
409 entries.add( new ChangeSet( endDate, "Upstream changes", "various", upstreamFiles ) );
410 }
411
412 return new ChangeLogSet( entries, startDate, endDate );
413 }
414
415 public ChangeLogScmResult changelog( ScmProviderRepository repo, ScmFileSet testFileSet, CommandParameters params )
416 throws ScmException
417 {
418
419 return (ChangeLogScmResult) execute( repo, testFileSet, params );
420 }
421
422 }