| package org.apache.maven.scm.provider.integrity; |
| |
| /* |
| * 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 com.mks.api.Command; |
| import com.mks.api.MultiValue; |
| import com.mks.api.Option; |
| import com.mks.api.response.APIException; |
| import com.mks.api.response.Field; |
| import com.mks.api.response.Item; |
| import com.mks.api.response.Response; |
| import com.mks.api.response.WorkItem; |
| import com.mks.api.response.WorkItemIterator; |
| import com.mks.api.si.SIModelTypeName; |
| import org.apache.maven.scm.ChangeFile; |
| import org.apache.maven.scm.ChangeSet; |
| import org.apache.maven.scm.ScmFile; |
| import org.apache.maven.scm.ScmFileStatus; |
| import org.apache.maven.scm.command.changelog.ChangeLogSet; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| import java.io.File; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * This class represents an MKS Integrity Sandbox and provides an encapsulation |
| * for executing typical Sandbox operations |
| * |
| * @author <a href="mailto:cletus@mks.com">Cletus D'Souza</a> |
| * @since 1.6 |
| */ |
| public class Sandbox |
| { |
| // Our date format |
| public static final SimpleDateFormat RLOG_DATEFORMAT = new SimpleDateFormat( "MMMMM d, yyyy - h:mm:ss a" ); |
| |
| // File Separator |
| private String fs = System.getProperty( "file.separator" ); |
| |
| // MKS API Session Object |
| private APISession api; |
| |
| // Other sandbox specific class variables |
| private Project siProject; |
| |
| private String sandboxDir; |
| |
| private String cpid; |
| |
| // Flag to indicate the overall add operation was successful |
| private boolean addSuccess; |
| |
| // Flag to indicate the overall check-in operation was successful |
| private boolean ciSuccess; |
| |
| /** |
| * Fixes the default includes/excludes patterns for compatibility with MKS Integrity's 'si viewnonmembers' command |
| * |
| * @param pattern String pattern representing the includes/excludes file/directory list |
| */ |
| public static String formatFilePatterns( String pattern ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| if ( null != pattern && pattern.length() > 0 ) |
| { |
| String[] tokens = StringUtils.split( pattern, "," ); |
| for ( int i = 0; i < tokens.length; i++ ) |
| { |
| String tkn = tokens[i].trim(); |
| if ( tkn.indexOf( "file:" ) != 0 && tkn.indexOf( "dir:" ) != 0 ) |
| { |
| sb.append( tkn.indexOf( '.' ) > 0 |
| ? StringUtils.replaceOnce( tkn, "**/", "file:" ) |
| : StringUtils.replaceOnce( tkn, "**/", "dir:" ) ); |
| } |
| else |
| { |
| sb.append( tkn ); |
| } |
| sb.append( i < tokens.length ? "," : "" ); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * The Sandbox constructor |
| * |
| * @param api MKS API Session object |
| * @param cmProject Project object |
| * @param dir Absolute path to the location for the Sandbox directory |
| */ |
| public Sandbox( APISession api, Project cmProject, String dir ) |
| { |
| siProject = cmProject; |
| sandboxDir = dir; |
| this.api = api; |
| cpid = System.getProperty( "maven.scm.integrity.cpid" ); |
| cpid = ( ( null == cpid || cpid.length() == 0 ) ? ":none" : cpid ); |
| addSuccess = true; |
| ciSuccess = true; |
| } |
| |
| /** |
| * Attempts to figure out if the current sandbox already exists and is valid |
| * |
| * @param sandbox The client-side fully qualified path to the sandbox pj |
| * @return true/false depending on whether or not this location has a valid sandbox |
| * @throws APIException |
| */ |
| private boolean isValidSandbox( String sandbox ) |
| throws APIException |
| { |
| Command cmd = new Command( Command.SI, "sandboxinfo" ); |
| cmd.addOption( new Option( "sandbox", sandbox ) ); |
| |
| api.getLogger().debug( "Validating existing sandbox: " + sandbox ); |
| Response res = api.runCommand( cmd ); |
| WorkItemIterator wit = res.getWorkItems(); |
| try |
| { |
| WorkItem wi = wit.next(); |
| return wi.getField( "fullConfigSyntax" ).getValueAsString().equalsIgnoreCase( |
| siProject.getConfigurationPath() ); |
| } |
| catch ( APIException aex ) |
| { |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit code " + eh.getExitCode() ); |
| return false; |
| } |
| } |
| |
| /** |
| * Inspects the MKS API Response object's Item field to determine whether or nor a working file delta exists |
| * |
| * @param wfdelta MKS API Response object's Item representing the Working File Delta |
| * @return true if the working file is a delta; false otherwise |
| */ |
| private boolean isDelta( Item wfdelta ) |
| { |
| // Return false if there is no working file |
| return wfdelta.getField( "isDelta" ).getBoolean().booleanValue(); |
| } |
| |
| /** |
| * Executes a 'si add' command using the message for the description |
| * |
| * @param memberFile Full path to the new member's location |
| * @param message Description for the new member's archive |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| private Response add( File memberFile, String message ) |
| throws APIException |
| { |
| // Setup the add command |
| api.getLogger().info( "Adding member: " + memberFile.getAbsolutePath() ); |
| Command siAdd = new Command( Command.SI, "add" ); |
| siAdd.addOption( new Option( "onExistingArchive", "sharearchive" ) ); |
| siAdd.addOption( new Option( "cpid", cpid ) ); |
| if ( null != message && message.length() > 0 ) |
| { |
| siAdd.addOption( new Option( "description", message ) ); |
| } |
| siAdd.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| siAdd.addSelection( memberFile.getName() ); |
| return api.runCommand( siAdd ); |
| } |
| |
| /** |
| * Executes a 'si ci' command using the relativeName for the member name and message for the description |
| * |
| * @param memberFile Full path to the member's current sandbox location |
| * @param relativeName Relative path from the nearest subproject or project |
| * @param message Description for checking in the new update |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| private Response checkin( File memberFile, String relativeName, String message ) |
| throws APIException |
| { |
| // Setup the check-in command |
| api.getLogger().info( "Checking in member: " + memberFile.getAbsolutePath() ); |
| Command sici = new Command( Command.SI, "ci" ); |
| sici.addOption( new Option( "cpid", cpid ) ); |
| if ( null != message && message.length() > 0 ) |
| { |
| sici.addOption( new Option( "description", message ) ); |
| } |
| sici.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| sici.addSelection( relativeName ); |
| return api.runCommand( sici ); |
| } |
| |
| /** |
| * Executes a 'si drop' command using the relativeName for the member name |
| * |
| * @param memberFile Full path to the member's current sandbox location |
| * @param relativeName Relative path from the nearest subproject or project |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| private Response dropMember( File memberFile, String relativeName ) |
| throws APIException |
| { |
| // Setup the drop command |
| api.getLogger().info( "Dropping member " + memberFile.getAbsolutePath() ); |
| Command siDrop = new Command( Command.SI, "drop" ); |
| siDrop.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| siDrop.addOption( new Option( "noconfirm" ) ); |
| siDrop.addOption( new Option( "cpid", cpid ) ); |
| siDrop.addOption( new Option( "delete" ) ); |
| siDrop.addSelection( relativeName ); |
| return api.runCommand( siDrop ); |
| } |
| |
| /** |
| * Executes a 'si diff' command to see if the working file has actually changed. Even though the |
| * working file delta might be true, that doesn't always mean the file has actually changed. |
| * |
| * @param memberFile Full path to the member's current sandbox location |
| * @param relativeName Relative path from the nearest subproject or project |
| * @return MKS API Response object |
| */ |
| private boolean hasMemberChanged( File memberFile, String relativeName ) |
| { |
| // Setup the differences command |
| Command siDiff = new Command( Command.SI, "diff" ); |
| siDiff.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| siDiff.addSelection( relativeName ); |
| try |
| { |
| // Run the diff command... |
| Response res = api.runCommand( siDiff ); |
| try |
| { |
| // Return the changed flag... |
| return res.getWorkItems().next().getResult().getField( "resultant" ).getItem().getField( |
| "different" ).getBoolean().booleanValue(); |
| } |
| catch ( NullPointerException npe ) |
| { |
| api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); |
| api.getLogger().warn( |
| "Null value found along response object for WorkItem/Result/Field/Item/Field.getBoolean()" ); |
| api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); |
| } |
| } |
| catch ( APIException aex ) |
| { |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); |
| api.getLogger().warn( eh.getMessage() ); |
| api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the full path name to the current Sandbox directory |
| * |
| * @return |
| */ |
| public String getSandboxDir() |
| { |
| return sandboxDir; |
| } |
| |
| /** |
| * Executes a 'si lock' command using the relativeName of the file |
| * |
| * @param memberFile Full path to the member's current sandbox location |
| * @param relativeName Relative path from the nearest subproject or project |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| public Response lock( File memberFile, String relativeName ) |
| throws APIException |
| { |
| // Setup the lock command |
| api.getLogger().debug( "Locking member: " + memberFile.getAbsolutePath() ); |
| Command siLock = new Command( Command.SI, "lock" ); |
| siLock.addOption( new Option( "revision", ":member" ) ); |
| siLock.addOption( new Option( "cpid", cpid ) ); |
| siLock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| siLock.addSelection( relativeName ); |
| // Execute the lock command |
| return api.runCommand( siLock ); |
| } |
| |
| /** |
| * Executes a 'si unlock' command using the relativeName of the file |
| * |
| * @param memberFile Full path to the member's current sandbox location |
| * @param relativeName Relative path from the nearest subproject or project |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| public Response unlock( File memberFile, String relativeName ) |
| throws APIException |
| { |
| // Setup the unlock command |
| api.getLogger().debug( "Unlocking member: " + memberFile.getAbsolutePath() ); |
| Command siUnlock = new Command( Command.SI, "unlock" ); |
| siUnlock.addOption( new Option( "revision", ":member" ) ); |
| siUnlock.addOption( new Option( "action", "remove" ) ); |
| siUnlock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); |
| siUnlock.addSelection( relativeName ); |
| // Execute the unlock command |
| return api.runCommand( siUnlock ); |
| } |
| |
| /** |
| * Removes the registration for the Sandbox in the user's profile |
| * |
| * @return The API Response associated with executing this command |
| * @throws APIException |
| */ |
| public Response drop() |
| throws APIException |
| { |
| File project = new File( siProject.getProjectName() ); |
| File sandboxpj = new File( sandboxDir + fs + project.getName() ); |
| |
| // Check to see if the sandbox file already exists and its OK to use |
| api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); |
| Command cmd = new Command( Command.SI, "dropsandbox" ); |
| cmd.addOption( new Option( "delete", "members" ) ); |
| cmd.addOption( new Option( "sandbox", sandboxpj.getAbsolutePath() ) ); |
| cmd.addOption( new Option( "cwd", sandboxDir ) ); |
| return api.runCommand( cmd ); |
| } |
| |
| /** |
| * Creates a new Sandbox in the sandboxDir specified |
| * |
| * @return true if the operation is successful; false otherwise |
| * @throws APIException |
| */ |
| public boolean create() |
| throws APIException |
| { |
| File project = new File( siProject.getProjectName() ); |
| File sandboxpj = new File( sandboxDir + fs + project.getName() ); |
| |
| // Check to see if the sandbox file already exists and its OK to use |
| api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); |
| if ( sandboxpj.isFile() ) |
| { |
| // Validate this sandbox |
| if ( isValidSandbox( sandboxpj.getAbsolutePath() ) ) |
| { |
| api.getLogger().debug( |
| "Reusing existing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); |
| return true; |
| } |
| else |
| { |
| api.getLogger().error( |
| "An invalid Sandbox exists in " + sandboxDir + ". Please provide a different location!" ); |
| return false; |
| } |
| } |
| else // Create a new sandbox in the location specified |
| { |
| api.getLogger().debug( |
| "Creating Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); |
| try |
| { |
| Command cmd = new Command( Command.SI, "createsandbox" ); |
| cmd.addOption( new Option( "recurse" ) ); |
| cmd.addOption( new Option( "nopopulate" ) ); |
| cmd.addOption( new Option( "project", siProject.getConfigurationPath() ) ); |
| cmd.addOption( new Option( "cwd", sandboxDir ) ); |
| api.runCommand( cmd ); |
| } |
| catch ( APIException aex ) |
| { |
| // Check to see if this exception is due an existing sandbox registry entry |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| if ( eh.getMessage().indexOf( "There is already a registered entry" ) > 0 ) |
| { |
| // This will re-validate the sandbox, if Maven blew away the old directory |
| return create(); |
| } |
| else |
| { |
| throw aex; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Resynchronizes an existing Sandbox |
| * Assumes that the create() call has already been made to validate this sandbox |
| * |
| * @throws APIException |
| */ |
| public Response resync() |
| throws APIException |
| { |
| api.getLogger().debug( |
| "Resynchronizing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); |
| Command cmd = new Command( Command.SI, "resync" ); |
| cmd.addOption( new Option( "recurse" ) ); |
| cmd.addOption( new Option( "populate" ) ); |
| cmd.addOption( new Option( "cwd", sandboxDir ) ); |
| return api.runCommand( cmd ); |
| } |
| |
| /** |
| * Executes a 'si makewritable' command to allow edits to all files in the Sandbox directory |
| * |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| public Response makeWriteable() |
| throws APIException |
| { |
| api.getLogger().debug( |
| "Setting files to writeable in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); |
| Command cmd = new Command( Command.SI, "makewritable" ); |
| cmd.addOption( new Option( "recurse" ) ); |
| cmd.addOption( new Option( "cwd", sandboxDir ) ); |
| return api.runCommand( cmd ); |
| } |
| |
| /** |
| * Executes a 'si revert' command to roll back changes to all files in the Sandbox directory |
| * |
| * @return MKS API Response object |
| * @throws APIException |
| */ |
| public Response revertMembers() |
| throws APIException |
| { |
| api.getLogger().debug( |
| "Reverting changes in sandbox " + sandboxDir + " for project " + siProject.getConfigurationPath() ); |
| Command cmd = new Command( Command.SI, "revert" ); |
| cmd.addOption( new Option( "recurse" ) ); |
| cmd.addOption( new Option( "cwd", sandboxDir ) ); |
| return api.runCommand( cmd ); |
| } |
| |
| /** |
| * Executes a 'si viewnonmembers' command filtering the results using the exclude and include lists |
| * |
| * @param exclude Pattern containing the exclude file list |
| * @param include Pattern containing the include file list |
| * @return List of ScmFile objects representing the new files in the Sandbox |
| * @throws APIException |
| */ |
| public List<ScmFile> getNewMembers( String exclude, String include ) |
| throws APIException |
| { |
| // Store a list of files that were added to the repository |
| List<ScmFile> filesAdded = new ArrayList<ScmFile>(); |
| Command siViewNonMem = new Command( Command.SI, "viewnonmembers" ); |
| siViewNonMem.addOption( new Option( "recurse" ) ); |
| if ( null != exclude && exclude.length() > 0 ) |
| { |
| siViewNonMem.addOption( new Option( "exclude", exclude ) ); |
| } |
| if ( null != include && include.length() > 0 ) |
| { |
| siViewNonMem.addOption( new Option( "include", include ) ); |
| } |
| siViewNonMem.addOption( new Option( "noincludeFormers" ) ); |
| siViewNonMem.addOption( new Option( "cwd", sandboxDir ) ); |
| Response response = api.runCommand( siViewNonMem ); |
| for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) |
| { |
| filesAdded.add( |
| new ScmFile( wit.next().getField( "absolutepath" ).getValueAsString(), ScmFileStatus.ADDED ) ); |
| } |
| return filesAdded; |
| |
| } |
| |
| /** |
| * Adds a list of files to the MKS Integrity SCM Project |
| * |
| * @param exclude Pattern containing the exclude file list |
| * @param include Pattern containing the include file list |
| * @param message Description for the member's archive |
| * @return |
| */ |
| public List<ScmFile> addNonMembers( String exclude, String include, String message ) |
| { |
| // Re-initialize the overall addSuccess to be true for now |
| addSuccess = true; |
| // Store a list of files that were actually added to the repository |
| List<ScmFile> filesAdded = new ArrayList<ScmFile>(); |
| api.getLogger().debug( "Looking for new members in sandbox dir: " + sandboxDir ); |
| try |
| { |
| List<ScmFile> newFileList = getNewMembers( exclude, include ); |
| for ( Iterator<ScmFile> sit = newFileList.iterator(); sit.hasNext(); ) |
| { |
| try |
| { |
| ScmFile localFile = sit.next(); |
| // Attempt to add the file to the Integrity repository |
| add( new File( localFile.getPath() ), message ); |
| // If it was a success, then add it to the list of files that were actually added |
| filesAdded.add( localFile ); |
| } |
| catch ( APIException aex ) |
| { |
| // Set the addSuccess to false, since we ran into a problem |
| addSuccess = false; |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); |
| } |
| } |
| } |
| catch ( APIException aex ) |
| { |
| // Set the addSuccess to false, since we ran into a problem |
| addSuccess = false; |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); |
| } |
| return filesAdded; |
| } |
| |
| /** |
| * Returns the overall success of the add operation |
| * |
| * @return |
| */ |
| public boolean getOverallAddSuccess() |
| { |
| return addSuccess; |
| } |
| |
| /** |
| * Inspects the MKS API Response object's Item field to determine whether or nor a working file exists |
| * |
| * @param wfdelta MKS API Response object's Item representing the Working File Delta |
| * @return |
| */ |
| public boolean hasWorkingFile( Item wfdelta ) |
| { |
| // Return false if there is no working file |
| return !wfdelta.getField( "noWorkingFile" ).getBoolean().booleanValue(); |
| } |
| |
| /** |
| * Executes a 'si viewsandbox' and parses the output for changed or dropped working files |
| * |
| * @return A list of MKS API Response WorkItem objects representing the changes in the Sandbox |
| * @throws APIException |
| */ |
| public List<WorkItem> getChangeList() |
| throws APIException |
| { |
| // Store a list of files that were changed/removed to the repository |
| List<WorkItem> changedFiles = new ArrayList<WorkItem>(); |
| // Setup the view sandbox command to figure out what has changed... |
| Command siViewSandbox = new Command( Command.SI, "viewsandbox" ); |
| // Create the --fields option |
| MultiValue mv = new MultiValue( "," ); |
| mv.add( "name" ); |
| mv.add( "context" ); |
| mv.add( "wfdelta" ); |
| mv.add( "memberarchive" ); |
| siViewSandbox.addOption( new Option( "fields", mv ) ); |
| siViewSandbox.addOption( new Option( "recurse" ) ); |
| siViewSandbox.addOption( new Option( "noincludeDropped" ) ); |
| siViewSandbox.addOption( new Option( "filterSubs" ) ); |
| siViewSandbox.addOption( new Option( "cwd", sandboxDir ) ); |
| |
| // Run the view sandbox command |
| Response r = api.runCommand( siViewSandbox ); |
| // Check-in all changed files, drop all members with missing working files |
| for ( WorkItemIterator wit = r.getWorkItems(); wit.hasNext(); ) |
| { |
| WorkItem wi = wit.next(); |
| api.getLogger().debug( "Inspecting file: " + wi.getField( "name" ).getValueAsString() ); |
| |
| if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) ) |
| { |
| Item wfdeltaItem = (Item) wi.getField( "wfdelta" ).getValue(); |
| // Proceed with this entry only if it is an actual working file delta |
| if ( isDelta( wfdeltaItem ) ) |
| { |
| File memberFile = new File( wi.getField( "name" ).getValueAsString() ); |
| if ( hasWorkingFile( wfdeltaItem ) ) |
| { |
| // Only report on files that have actually changed... |
| if ( hasMemberChanged( memberFile, wi.getId() ) ) |
| { |
| changedFiles.add( wi ); |
| } |
| } |
| else |
| { |
| // Also report on dropped files |
| changedFiles.add( wi ); |
| } |
| } |
| } |
| } |
| return changedFiles; |
| } |
| |
| /** |
| * Wrapper function to check-in all changes and drop members associated with missing working files |
| * |
| * @param message Description for the changes |
| * @return |
| */ |
| public List<ScmFile> checkInUpdates( String message ) |
| { |
| // Re-initialize the overall ciSuccess to be true for now |
| ciSuccess = true; |
| // Store a list of files that were changed/removed to the repository |
| List<ScmFile> changedFiles = new ArrayList<ScmFile>(); |
| api.getLogger().debug( "Looking for changed and dropped members in sandbox dir: " + sandboxDir ); |
| |
| try |
| { |
| // Let the list of changed files |
| List<WorkItem> changeList = getChangeList(); |
| // Check-in all changed files, drop all members with missing working files |
| for ( Iterator<WorkItem> wit = changeList.iterator(); wit.hasNext(); ) |
| { |
| try |
| { |
| WorkItem wi = wit.next(); |
| File memberFile = new File( wi.getField( "name" ).getValueAsString() ); |
| // Check-in files that have actually changed... |
| if ( hasWorkingFile( (Item) wi.getField( "wfdelta" ).getValue() ) ) |
| { |
| // Lock each member as you go... |
| lock( memberFile, wi.getId() ); |
| // Commit the changes... |
| checkin( memberFile, wi.getId(), message ); |
| // Update the changed file list |
| changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.CHECKED_IN ) ); |
| } |
| else |
| { |
| // Drop the member if there is no working file |
| dropMember( memberFile, wi.getId() ); |
| // Update the changed file list |
| changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.DELETED ) ); |
| } |
| } |
| catch ( APIException aex ) |
| { |
| // Set the ciSuccess to false, since we ran into a problem |
| ciSuccess = false; |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); |
| } |
| } |
| } |
| catch ( APIException aex ) |
| { |
| // Set the ciSuccess to false, since we ran into a problem |
| ciSuccess = false; |
| ExceptionHandler eh = new ExceptionHandler( aex ); |
| api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); |
| api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); |
| } |
| |
| return changedFiles; |
| } |
| |
| /** |
| * Returns the overall success of the check-in operation |
| * |
| * @return |
| */ |
| public boolean getOverallCheckInSuccess() |
| { |
| return ciSuccess; |
| } |
| |
| /** |
| * Creates one subproject per directory, as required. |
| * |
| * @param dirPath A relative path structure of folders |
| * @return Response containing the result for the created subproject |
| * @throws APIException |
| */ |
| public Response createSubproject( String dirPath ) |
| throws APIException |
| { |
| // Setup the create subproject command |
| api.getLogger().debug( "Creating subprojects for: " + dirPath + "/project.pj" ); |
| Command siCreateSubproject = new Command( Command.SI, "createsubproject" ); |
| siCreateSubproject.addOption( new Option( "cpid", cpid ) ); |
| siCreateSubproject.addOption( new Option( "createSubprojects" ) ); |
| siCreateSubproject.addOption( new Option( "cwd", sandboxDir ) ); |
| siCreateSubproject.addSelection( dirPath + "/project.pj" ); |
| // Execute the create subproject command |
| return api.runCommand( siCreateSubproject ); |
| } |
| |
| /** |
| * Executes the 'si rlog' command to generate a list of changed revision found between startDate and endDate |
| * |
| * @param startDate The date range for the beginning of the operation |
| * @param endDate The date range for the end of the operation |
| * @return ChangeLogSet containing a list of changes grouped by Change Pacakge ID |
| * @throws APIException |
| */ |
| public ChangeLogSet getChangeLog( Date startDate, Date endDate ) |
| throws APIException |
| { |
| // Initialize our return object |
| ChangeLogSet changeLog = new ChangeLogSet( startDate, endDate ); |
| // By default we're going to group-by change package |
| // Non change package changes will be lumped into one big Change Set |
| Hashtable<String, ChangeSet> changeSetHash = new Hashtable<String, ChangeSet>(); |
| |
| // Lets prepare our si rlog command for execution |
| Command siRlog = new Command( Command.SI, "rlog" ); |
| siRlog.addOption( new Option( "recurse" ) ); |
| MultiValue rFilter = new MultiValue( ":" ); |
| rFilter.add( "daterange" ); |
| rFilter.add( "'" + RLOG_DATEFORMAT.format( startDate ) + "'-'" + RLOG_DATEFORMAT.format( endDate ) + "'" ); |
| siRlog.addOption( new Option( "rfilter", rFilter ) ); |
| siRlog.addOption( new Option( "cwd", sandboxDir ) ); |
| // Execute the si rlog command |
| Response response = api.runCommand( siRlog ); |
| for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) |
| { |
| WorkItem wi = wit.next(); |
| String memberName = wi.getContext(); |
| // We're going to have to do a little dance to get the correct server file name |
| memberName = memberName.substring( 0, memberName.lastIndexOf( '/' ) ); |
| memberName = memberName + '/' + wi.getId(); |
| memberName = memberName.replace( '\\', '/' ); |
| // Now lets get the revisions for this file |
| Field revisionsFld = wi.getField( "revisions" ); |
| if ( null != revisionsFld && revisionsFld.getDataType().equals( Field.ITEM_LIST_TYPE ) |
| && null != revisionsFld.getList() ) |
| { |
| @SuppressWarnings( "unchecked" ) List<Item> revList = revisionsFld.getList(); |
| for ( Iterator<Item> lit = revList.iterator(); lit.hasNext(); ) |
| { |
| Item revisionItem = lit.next(); |
| String revision = revisionItem.getId(); |
| String author = revisionItem.getField( "author" ).getItem().getId(); |
| // Attempt to get the full name, if available |
| try |
| { |
| author = revisionItem.getField( "author" ).getItem().getField( "fullname" ).getValueAsString(); |
| } |
| catch ( NullPointerException npe ) |
| { /* ignore */ } |
| String cpid = ":none"; |
| // Attempt to get the cpid for this revision |
| try |
| { |
| cpid = revisionItem.getField( "cpid" ).getItem().getId(); |
| } |
| catch ( NullPointerException npe ) |
| { /* ignore */ } |
| // Get the Change Package summary for this revision |
| String comment = cpid + ": " + revisionItem.getField( "cpsummary" ).getValueAsString(); |
| // Get the date associated with this revision |
| Date date = revisionItem.getField( "date" ).getDateTime(); |
| |
| // Lets create our ChangeFile based on the information we've gathered so far |
| ChangeFile changeFile = new ChangeFile( memberName, revision ); |
| |
| // Check to see if we already have a ChangeSet grouping for this revision |
| ChangeSet changeSet = changeSetHash.get( cpid ); |
| if ( null != changeSet ) |
| { |
| // Set the date of the ChangeSet to the oldest entry |
| if ( changeSet.getDate().after( date ) ) |
| { |
| changeSet.setDate( date ); |
| } |
| // Add the new ChangeFile |
| changeSet.addFile( changeFile ); |
| // Update the changeSetHash |
| changeSetHash.put( cpid, changeSet ); |
| } |
| else // Create a new ChangeSet grouping and add the ChangeFile |
| { |
| List<ChangeFile> changeFileList = new ArrayList<ChangeFile>(); |
| changeFileList.add( changeFile ); |
| changeSet = new ChangeSet( date, comment, author, changeFileList ); |
| // Update the changeSetHash with an initial entry for the cpid |
| changeSetHash.put( cpid, changeSet ); |
| } |
| } |
| } |
| |
| } |
| |
| // Update the Change Log with the Change Sets |
| List<ChangeSet> changeSetList = new ArrayList<ChangeSet>(); |
| changeSetList.addAll( changeSetHash.values() ); |
| changeLog.setChangeSets( changeSetList ); |
| |
| return changeLog; |
| } |
| } |