| package org.apache.maven.scm.provider.accurev.command.changelog; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.apache.maven.scm.ChangeFile; |
| import org.apache.maven.scm.ChangeSet; |
| import org.apache.maven.scm.CommandParameter; |
| import org.apache.maven.scm.CommandParameters; |
| import org.apache.maven.scm.ScmBranch; |
| import org.apache.maven.scm.ScmException; |
| import org.apache.maven.scm.ScmFileSet; |
| import org.apache.maven.scm.ScmResult; |
| import org.apache.maven.scm.ScmRevision; |
| import org.apache.maven.scm.ScmVersion; |
| import org.apache.maven.scm.command.changelog.ChangeLogScmResult; |
| import org.apache.maven.scm.command.changelog.ChangeLogSet; |
| import org.apache.maven.scm.log.ScmLogger; |
| import org.apache.maven.scm.provider.ScmProviderRepository; |
| import org.apache.maven.scm.provider.accurev.AccuRev; |
| import org.apache.maven.scm.provider.accurev.AccuRevCapability; |
| import org.apache.maven.scm.provider.accurev.AccuRevException; |
| import org.apache.maven.scm.provider.accurev.AccuRevScmProviderRepository; |
| import org.apache.maven.scm.provider.accurev.AccuRevVersion; |
| import org.apache.maven.scm.provider.accurev.FileDifference; |
| import org.apache.maven.scm.provider.accurev.Stream; |
| import org.apache.maven.scm.provider.accurev.Transaction; |
| import org.apache.maven.scm.provider.accurev.Transaction.Version; |
| import org.apache.maven.scm.provider.accurev.command.AbstractAccuRevCommand; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * TODO filter results based on project_path Find appropriate start and end transaction ids from parameters. Streams |
| * must be the same. Diff on stream start to end - these are the upstream changes Hist on the stream start+1 to end |
| * remove items from the upstream set if they appear in the history For workspaces diff doesn't work. So we would not |
| * pickup any upstream changes, just the "keep" transactions which is not very useful. Hist on the workspace Then diff / |
| * hist on the basis stream, skipping any transactions that are coming from the workspace. |
| * |
| * @author ggardner |
| */ |
| public class AccuRevChangeLogCommand |
| extends AbstractAccuRevCommand |
| { |
| |
| public AccuRevChangeLogCommand( ScmLogger logger ) |
| { |
| super( logger ); |
| } |
| |
| @Override |
| protected ScmResult executeAccurevCommand( AccuRevScmProviderRepository repository, ScmFileSet fileSet, |
| CommandParameters parameters ) |
| throws ScmException, AccuRevException |
| { |
| |
| // Do we have a supplied branch. If not we default to the URL stream. |
| ScmBranch branch = (ScmBranch) parameters.getScmVersion( CommandParameter.BRANCH, null ); |
| AccuRevVersion branchVersion = repository.getAccuRevVersion( branch ); |
| String stream = branchVersion.getBasisStream(); |
| String fromSpec = branchVersion.getTimeSpec(); |
| String toSpec = "highest"; |
| |
| // Versions |
| ScmVersion startVersion = parameters.getScmVersion( CommandParameter.START_SCM_VERSION, null ); |
| ScmVersion endVersion = parameters.getScmVersion( CommandParameter.END_SCM_VERSION, null ); |
| |
| if ( startVersion != null && StringUtils.isNotEmpty( startVersion.getName() ) ) |
| { |
| AccuRevVersion fromVersion = repository.getAccuRevVersion( startVersion ); |
| // if no end version supplied then use same basis as startVersion |
| AccuRevVersion toVersion = |
| endVersion == null ? new AccuRevVersion( fromVersion.getBasisStream(), "now" ) |
| : repository.getAccuRevVersion( endVersion ); |
| |
| if ( !Objects.equals( fromVersion.getBasisStream(), toVersion.getBasisStream() ) ) |
| { |
| throw new AccuRevException( "Not able to provide change log between different streams " + fromVersion |
| + "," + toVersion ); |
| } |
| |
| stream = fromVersion.getBasisStream(); |
| fromSpec = fromVersion.getTimeSpec(); |
| toSpec = toVersion.getTimeSpec(); |
| |
| } |
| |
| Date startDate = parameters.getDate( CommandParameter.START_DATE, null ); |
| Date endDate = parameters.getDate( CommandParameter.END_DATE, null ); |
| int numDays = parameters.getInt( CommandParameter.NUM_DAYS, 0 ); |
| |
| if ( numDays > 0 ) |
| { |
| if ( ( startDate != null || endDate != null ) ) |
| { |
| throw new ScmException( "Start or end date cannot be set if num days is set." ); |
| } |
| // Last x days. |
| int day = 24 * 60 * 60 * 1000; |
| startDate = new Date( System.currentTimeMillis() - (long) numDays * day ); |
| endDate = new Date( System.currentTimeMillis() + day ); |
| } |
| |
| if ( endDate != null && startDate == null ) |
| { |
| throw new ScmException( "The end date is set but the start date isn't." ); |
| } |
| |
| // Date parameters override transaction ids in versions |
| if ( startDate != null ) |
| { |
| fromSpec = AccuRevScmProviderRepository.formatTimeSpec( startDate ); |
| } |
| else if ( fromSpec == null ) |
| { |
| fromSpec = "1"; |
| } |
| |
| // Convert the fromSpec to both a date AND a transaction id by looking up |
| // the nearest transaction in the depot. |
| Transaction fromTransaction = getDepotTransaction( repository, stream, fromSpec ); |
| |
| long fromTranId = 1; |
| if ( fromTransaction != null ) |
| { |
| // This tran id is less than or equal to the date/tranid we requested. |
| fromTranId = fromTransaction.getTranId(); |
| if ( startDate == null ) |
| { |
| startDate = fromTransaction.getWhen(); |
| } |
| } |
| |
| if ( endDate != null ) |
| { |
| toSpec = AccuRevScmProviderRepository.formatTimeSpec( endDate ); |
| } |
| else if ( toSpec == null ) |
| { |
| toSpec = "highest"; |
| } |
| |
| Transaction toTransaction = getDepotTransaction( repository, stream, toSpec ); |
| long toTranId = 1; |
| if ( toTransaction != null ) |
| { |
| toTranId = toTransaction.getTranId(); |
| if ( endDate == null ) |
| { |
| endDate = toTransaction.getWhen(); |
| } |
| } |
| startVersion = new ScmRevision( repository.getRevision( stream, fromTranId ) ); |
| endVersion = new ScmRevision( repository.getRevision( stream, toTranId ) ); |
| |
| //TODO Split this method in two here. above to convert params to start and end (stream,tranid,date) and test independantly |
| |
| List<Transaction> streamHistory = Collections.emptyList(); |
| List<Transaction> workspaceHistory = Collections.emptyList(); |
| List<FileDifference> streamDifferences = Collections.emptyList(); |
| |
| StringBuilder errorMessage = new StringBuilder(); |
| |
| AccuRev accurev = repository.getAccuRev(); |
| |
| Stream changelogStream = accurev.showStream( stream ); |
| if ( changelogStream == null ) |
| { |
| errorMessage.append( "Unknown accurev stream -" ).append( stream ).append( "." ); |
| } |
| else |
| { |
| |
| String message = |
| "Changelog on stream " + stream + "(" + changelogStream.getStreamType() + ") from " + fromTranId + " (" |
| + startDate + "), to " + toTranId + " (" + endDate + ")"; |
| |
| if ( startDate != null && startDate.after( endDate ) || fromTranId >= toTranId ) |
| { |
| getLogger().warn( "Skipping out of range " + message ); |
| } |
| else |
| { |
| |
| getLogger().info( message ); |
| |
| // In 4.7.2 and higher we have a diff command that will list all the file differences in a stream |
| // and thus can be used to detect upstream changes |
| // Unfortunately diff -v -V -t does not work in workspaces. |
| Stream diffStream = changelogStream; |
| if ( changelogStream.isWorkspace() ) |
| { |
| |
| workspaceHistory = |
| accurev.history( stream, Long.toString( fromTranId + 1 ), Long.toString( toTranId ), 0, false, |
| false ); |
| |
| if ( workspaceHistory == null ) |
| { |
| errorMessage.append( "history on workspace " + stream + " from " + fromTranId + 1 + " to " |
| + toTranId + " failed." ); |
| |
| } |
| |
| // do the diff/hist on the basis stream instead. |
| stream = changelogStream.getBasis(); |
| diffStream = accurev.showStream( stream ); |
| |
| } |
| |
| if ( AccuRevCapability.DIFF_BETWEEN_STREAMS.isSupported( accurev.getClientVersion() ) ) |
| { |
| if ( startDate.before( diffStream.getStartDate() ) ) |
| { |
| getLogger().warn( "Skipping diff of " + stream + " due to start date out of range" ); |
| } |
| else |
| { |
| streamDifferences = |
| accurev.diff( stream, Long.toString( fromTranId ), Long.toString( toTranId ) ); |
| if ( streamDifferences == null ) |
| { |
| errorMessage.append( "Diff " + stream + "- " + fromTranId + " to " + toTranId + "failed." ); |
| } |
| } |
| } |
| |
| // History needs to start from the transaction after our starting transaction |
| |
| streamHistory = |
| accurev.history( stream, Long.toString( fromTranId + 1 ), Long.toString( toTranId ), 0, false, |
| false ); |
| if ( streamHistory == null ) |
| { |
| errorMessage.append( "history on stream " + stream + " from " + fromTranId + 1 + " to " + toTranId |
| + " failed." ); |
| } |
| |
| } |
| } |
| |
| String errorString = errorMessage.toString(); |
| if ( StringUtils.isBlank( errorString ) ) |
| { |
| ChangeLogSet changeLog = |
| getChangeLog( changelogStream, streamDifferences, streamHistory, workspaceHistory, startDate, endDate ); |
| |
| changeLog.setEndVersion( endVersion ); |
| changeLog.setStartVersion( startVersion ); |
| |
| return new ChangeLogScmResult( accurev.getCommandLines(), changeLog ); |
| } |
| else |
| { |
| return new ChangeLogScmResult( accurev.getCommandLines(), "AccuRev errors: " + errorMessage, |
| accurev.getErrorOutput(), false ); |
| } |
| |
| } |
| |
| private Transaction getDepotTransaction( AccuRevScmProviderRepository repo, String stream, String tranSpec ) |
| throws AccuRevException |
| { |
| return repo.getDepotTransaction( stream, tranSpec ); |
| } |
| |
| private ChangeLogSet getChangeLog( Stream stream, List<FileDifference> streamDifferences, |
| List<Transaction> streamHistory, List<Transaction> workspaceHistory, |
| Date startDate, Date endDate ) |
| { |
| |
| // Collect all the "to" versions from the streamDifferences into a Map by element id |
| // If that version is seen in the promote/keep history then we move it from the map |
| // At the end we create a pseudo ChangeSet for any remaining entries in the map as |
| // representing "upstream changes" |
| Map<Long, FileDifference> differencesMap = new HashMap<Long, FileDifference>(); |
| for ( FileDifference fileDifference : streamDifferences ) |
| { |
| differencesMap.put( fileDifference.getElementId(), fileDifference ); |
| } |
| |
| List<Transaction> mergedHistory = new ArrayList<Transaction>( streamHistory ); |
| // will never match a version |
| String streamPrefix = "/"; |
| |
| mergedHistory.addAll( workspaceHistory ); |
| streamPrefix = stream.getId() + "/"; |
| |
| List<ChangeSet> entries = new ArrayList<ChangeSet>( streamHistory.size() ); |
| for ( Transaction t : mergedHistory ) |
| { |
| if ( ( startDate != null && t.getWhen().before( startDate ) ) |
| || ( endDate != null && t.getWhen().after( endDate ) ) ) |
| { |
| // This is possible if dates and transactions are mixed in the time spec. |
| continue; |
| } |
| |
| // Needed to make Tck test pass against accurev > 4.7.2 - the changelog only expects to deal with |
| // files. Stream changes and cross links are important entries in the changelog. |
| // However we should only see mkstream once and it is irrelevant given we are interrogating |
| // the history of this stream. |
| if ( "mkstream".equals( t.getTranType() ) ) |
| { |
| continue; |
| } |
| |
| Collection<Version> versions = t.getVersions(); |
| List<ChangeFile> files = new ArrayList<ChangeFile>( versions.size() ); |
| |
| for ( Version v : versions ) |
| { |
| |
| // Remove diff representing this promote |
| FileDifference difference = differencesMap.get( v.getElementId() ); |
| // TODO: how are defuncts shown in the version history? |
| if ( difference != null ) |
| { |
| String newVersionSpec = difference.getNewVersionSpec(); |
| if ( newVersionSpec != null && newVersionSpec.equals( v.getRealSpec() ) ) |
| { |
| if ( getLogger().isDebugEnabled() ) |
| { |
| getLogger().debug( "Removing difference for " + v ); |
| } |
| differencesMap.remove( v.getElementId() ); |
| } |
| } |
| |
| // Add this file, unless the virtual version indicates this is the basis stream, and the real |
| // version came from our workspace stream (ie, this transaction is a promote from the workspace |
| // to its basis stream, and is therefore NOT a change |
| if ( v.getRealSpec().startsWith( streamPrefix ) && !v.getVirtualSpec().startsWith( streamPrefix ) ) |
| { |
| if ( getLogger().isDebugEnabled() ) |
| { |
| getLogger().debug( "Skipping workspace to basis stream promote " + v ); |
| } |
| } |
| else |
| { |
| ChangeFile f = |
| new ChangeFile( v.getElementName(), v.getVirtualSpec() + " (" + v.getRealSpec() + ")" ); |
| files.add( f ); |
| } |
| |
| } |
| |
| if ( versions.isEmpty() || !files.isEmpty() ) |
| { |
| ChangeSet changeSet = new ChangeSet( t.getWhen(), t.getComment(), t.getAuthor(), files ); |
| |
| entries.add( changeSet ); |
| } |
| else |
| { |
| if ( getLogger().isDebugEnabled() ) |
| { |
| getLogger().debug( "All versions removed for " + t ); |
| } |
| } |
| |
| } |
| |
| // Anything left in the differencesMap represents a change from a higher stream |
| // We don't have details on who or where these came from, but it is important to |
| // detect these for CI tools like Continuum |
| if ( !differencesMap.isEmpty() ) |
| { |
| List<ChangeFile> upstreamFiles = new ArrayList<ChangeFile>(); |
| for ( FileDifference difference : differencesMap.values() ) |
| { |
| if ( difference.getNewVersionSpec() != null ) |
| { |
| upstreamFiles.add( new ChangeFile( difference.getNewFile().getPath(), |
| difference.getNewVersionSpec() ) ); |
| } |
| else |
| { |
| // difference is a deletion |
| upstreamFiles.add( new ChangeFile( difference.getOldFile().getPath(), null ) ); |
| } |
| } |
| entries.add( new ChangeSet( endDate, "Upstream changes", "various", upstreamFiles ) ); |
| } |
| |
| return new ChangeLogSet( entries, startDate, endDate ); |
| } |
| |
| public ChangeLogScmResult changelog( ScmProviderRepository repo, ScmFileSet testFileSet, CommandParameters params ) |
| throws ScmException |
| { |
| return (ChangeLogScmResult) execute( repo, testFileSet, params ); |
| } |
| |
| } |