001package 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 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Date; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import org.apache.maven.scm.ChangeFile; 031import org.apache.maven.scm.ChangeSet; 032import org.apache.maven.scm.CommandParameter; 033import org.apache.maven.scm.CommandParameters; 034import org.apache.maven.scm.ScmBranch; 035import org.apache.maven.scm.ScmException; 036import org.apache.maven.scm.ScmFileSet; 037import org.apache.maven.scm.ScmResult; 038import org.apache.maven.scm.ScmRevision; 039import org.apache.maven.scm.ScmVersion; 040import org.apache.maven.scm.command.changelog.ChangeLogScmResult; 041import org.apache.maven.scm.command.changelog.ChangeLogSet; 042import org.apache.maven.scm.log.ScmLogger; 043import org.apache.maven.scm.provider.ScmProviderRepository; 044import org.apache.maven.scm.provider.accurev.AccuRev; 045import org.apache.maven.scm.provider.accurev.AccuRevCapability; 046import org.apache.maven.scm.provider.accurev.AccuRevException; 047import org.apache.maven.scm.provider.accurev.AccuRevScmProviderRepository; 048import org.apache.maven.scm.provider.accurev.AccuRevVersion; 049import org.apache.maven.scm.provider.accurev.FileDifference; 050import org.apache.maven.scm.provider.accurev.Stream; 051import org.apache.maven.scm.provider.accurev.Transaction; 052import org.apache.maven.scm.provider.accurev.Transaction.Version; 053import org.apache.maven.scm.provider.accurev.command.AbstractAccuRevCommand; 054import 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 */ 065public 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}