001package org.apache.maven.scm.provider.jazz.command.checkin;
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.io.File;
023import java.util.List;
024
025import org.apache.maven.scm.ScmException;
026import org.apache.maven.scm.ScmFileSet;
027import org.apache.maven.scm.ScmVersion;
028import org.apache.maven.scm.command.add.AddScmResult;
029import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
030import org.apache.maven.scm.command.checkin.CheckInScmResult;
031import org.apache.maven.scm.provider.ScmProviderRepository;
032import org.apache.maven.scm.provider.jazz.command.JazzConstants;
033import org.apache.maven.scm.provider.jazz.command.JazzScmCommand;
034import org.apache.maven.scm.provider.jazz.command.add.JazzAddCommand;
035import org.apache.maven.scm.provider.jazz.command.consumer.DebugLoggerConsumer;
036import org.apache.maven.scm.provider.jazz.command.consumer.ErrorConsumer;
037import org.apache.maven.scm.provider.jazz.command.status.JazzStatusCommand;
038import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
039import org.codehaus.plexus.util.StringUtils;
040import org.codehaus.plexus.util.cli.StreamConsumer;
041
042// The Maven SCM Plugin "checkin" goal is equivalent to the RTC "checkin" command.
043//
044// This implementation of the Maven SCM Plugin "checkin" goal creates a change set with the message provided.
045// It then uses the Jazz "scm "checkin" command to check the files into a remote workspace.
046// If there is a flow target defined and the pushChanges flag is true (the default), then the remote workspace
047// will be delivered ("scm deliver") to the flow target (a stream or other workspace).
048// 
049// Set the pushChanges flag to false, if you do not want the repository workspace delivered.
050//
051// NOTE: At this point, only a SINGLE flow target is supported. Jazz itself, allows for more than one.
052//
053// The differences between this and the "add" goal, are:
054//      - The add goal will only checkin into the remote repository workspace.
055//      - The add goal will never deliver.
056//      - The add goal does not create a change set.
057//
058// This is the best we can do to mimic the implementations of the other providers, that provide a working
059// "add" function (eg "svn add").
060//
061// Add may have had been able to use the "scm share" command, but that is recusive and only takes directory
062// names; we are not able to specify specific or single files.
063//
064// See the following links for additional information on the RTC "checkin" command.
065// RTC 2.0.0.2:
066// http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_checkin.html
067// RTC 3.0:
068// http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_checkin.html
069// RTC 3.0.1:
070// http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_checkin.html
071//
072// See the following links for additional information on the RTC "deliver" command.
073// RTC 2.0.0.2:
074// http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_deliver.html
075// RTC 3.0:
076// http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_deliver.html
077// RTC 3.0.1:
078// http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_deliver.html
079//
080
081/**
082 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
083 */
084public class JazzCheckInCommand
085    extends AbstractCheckInCommand
086{
087
088    /**
089     * {@inheritDoc}
090     */
091    protected CheckInScmResult executeCheckInCommand( ScmProviderRepository repository, ScmFileSet fileSet,
092                                                      String message, ScmVersion scmVersion )
093        throws ScmException
094    {
095        if ( scmVersion != null && StringUtils.isNotEmpty( scmVersion.getName() ) )
096        {
097            throw new ScmException( "This provider command can't handle tags." );
098        }
099
100        if ( getLogger().isDebugEnabled() )
101        {
102            getLogger().debug( "Executing checkin command..." );
103        }
104
105        // Create a changeset. We need to do this, as otherwise the information contained in the message
106        // will be lost forever.
107        JazzScmCommand createChangesetCmd = createCreateChangesetCommand( repository, fileSet, message );
108        DebugLoggerConsumer outputConsumer = new DebugLoggerConsumer( getLogger() );
109        ErrorConsumer errConsumer = new ErrorConsumer( getLogger() );
110
111        int status = createChangesetCmd.execute( outputConsumer, errConsumer );
112        if ( status != 0 )
113        {
114            return new CheckInScmResult( createChangesetCmd.getCommandString(),
115                                         "Error code for Jazz SCM create changeset command - " + status,
116                                         errConsumer.getOutput(), false );
117        }
118
119        // As we just created a change set, we now need to call the status command so we can parse the 
120        // newly created change set.
121
122        JazzStatusCommand statusCommand = new JazzStatusCommand();
123        statusCommand.setLogger( getLogger() );
124        statusCommand.executeStatusCommand( repository, fileSet );
125
126        // NOTE: For isPushChangesAndHaveFlowTargets() to work, a scm status call must have been called first!!!
127        // As the Workspace name and alias, and the Flow Target name and alias are needed.
128        
129        // Check to see if we've got a flow target and had a workItem defined (via -DworkItem=XXXX)
130        JazzScmProviderRepository jazzRepo = (JazzScmProviderRepository) repository;
131        if ( jazzRepo.isPushChangesAndHaveFlowTargets() && StringUtils.isNotEmpty( jazzRepo.getWorkItem() ) )
132        {
133            List<Integer> changeSetAliases = jazzRepo.getOutgoingChangeSetAliases();
134            if ( changeSetAliases != null && !changeSetAliases.isEmpty() )
135            {
136                for ( Integer changeSetAlias : changeSetAliases )
137                {
138                    // Associate a work item if we need too.
139                    JazzScmCommand changesetAssociateCmd = createChangesetAssociateCommand( repository, 
140                        changeSetAlias );
141                    outputConsumer = new DebugLoggerConsumer( getLogger() );
142                    errConsumer = new ErrorConsumer( getLogger() );
143        
144                    status = changesetAssociateCmd.execute( outputConsumer, errConsumer );
145                    if ( status != 0 )
146                    {
147                        return new CheckInScmResult( changesetAssociateCmd.getCommandString(),
148                                                     "Error code for Jazz SCM changeset associate command - " + status,
149                                                     errConsumer.getOutput(), false );
150                    }
151                }
152            }
153        }
154        
155        // Now check in the files themselves.
156        return executeCheckInCommand( repository, fileSet, scmVersion );
157    }
158
159    protected CheckInScmResult executeCheckInCommand( ScmProviderRepository repo, ScmFileSet fileSet,
160                                                      ScmVersion scmVersion )
161        throws ScmException
162    {
163        // Call the Add command to perform the checkin into the repository workspace.
164        JazzAddCommand addCommand = new JazzAddCommand();
165        addCommand.setLogger( getLogger() );
166        AddScmResult addResult = addCommand.executeAddCommand( repo, fileSet );
167
168        // Now, if it has a flow target, deliver it.
169        JazzScmProviderRepository jazzRepo = (JazzScmProviderRepository) repo;
170        if ( jazzRepo.isPushChangesAndHaveFlowTargets() )
171        {
172            // Push if we need too
173            JazzScmCommand deliverCmd = createDeliverCommand( (JazzScmProviderRepository) repo, fileSet );
174            StreamConsumer deliverConsumer =
175                new DebugLoggerConsumer( getLogger() );      // No need for a dedicated consumer for this
176            ErrorConsumer errConsumer = new ErrorConsumer( getLogger() );
177
178            int status = deliverCmd.execute( deliverConsumer, errConsumer );
179            if ( status != 0 )
180            {
181                return new CheckInScmResult( deliverCmd.getCommandString(),
182                                             "Error code for Jazz SCM deliver command - " + status,
183                                             errConsumer.getOutput(), false );
184            }
185        }
186
187        // Return what was added.
188        return new CheckInScmResult( addResult.getCommandLine(), addResult.getAddedFiles() );
189    }
190
191    public JazzScmCommand createCreateChangesetCommand( ScmProviderRepository repo, ScmFileSet fileSet, String message )
192    {
193        JazzScmCommand command =
194            new JazzScmCommand( JazzConstants.CMD_CREATE, JazzConstants.CMD_SUB_CHANGESET, repo, false, fileSet,
195                                getLogger() );
196        command.addArgument( message );
197
198        return command;
199    }
200
201    public JazzScmCommand createChangesetAssociateCommand( ScmProviderRepository repo, Integer changeSetAlias )
202    {
203        JazzScmCommand command =
204            new JazzScmCommand( JazzConstants.CMD_CHANGESET, JazzConstants.CMD_SUB_ASSOCIATE, repo, false, null,
205                                getLogger() );
206        // Add the change set alias
207        JazzScmProviderRepository jazzRepo = (JazzScmProviderRepository) repo;
208        // SCM-812 - Jazz SCM Alias Id's roll over to zero, not 1000 as advertised.
209        //           So, we need to add the changeSetAlias with leading zeros.
210        command.addArgument( StringUtils.leftPad( changeSetAlias.toString(), 4, "0" ) );
211        // Add the work item number
212        command.addArgument( jazzRepo.getWorkItem() );
213        return command;
214    }
215
216    public JazzScmCommand createCheckInCommand( ScmProviderRepository repo, ScmFileSet fileSet )
217    {
218        JazzScmCommand command =
219            new JazzScmCommand( JazzConstants.CMD_CHECKIN, null, repo, false, fileSet, getLogger() );
220
221        // TODO, this was taken out to quickly test how the release plugin works.
222        // The release plugin has the fileSet.getbaseDir() as the project it is checking in
223        // This happens to be a folder under the sandbox root, and so the checkin would fail because it needs
224        // to check in at the sandbox root level (not sub folders)
225        // The SCM Plugin has a basedir parameter that you can pass it, so everythig works ok from the scm-plugin alone
226        // but the release-plugin doesn't look like it lets you do that. (or I didn't have enough time
227        // to figure out how to do it properly).
228
229        // if (fileSet != null) {
230        // command.addArgument(JazzConstants.LOAD_ROOT_DIRECTORY_ARG);
231        // command.addArgument(fileSet.getBasedir().getAbsolutePath());
232        // }
233
234        List<File> files = fileSet.getFileList();
235        if ( files != null && !files.isEmpty() )
236        {
237            for ( File file : files )
238            {
239                command.addArgument( file.getPath() ); // Check in only the files specified
240            }
241        }
242        else
243        {
244            command.addArgument( "." ); // This will check in all local changes
245        }
246
247        return command;
248    }
249
250    // Create the JazzScmCommand to execute the "scm deliver ..." command
251    // This will deliver the changes to the flow target (stream or other workspace).
252    public JazzScmCommand createDeliverCommand( JazzScmProviderRepository repo, ScmFileSet fileSet )
253    {
254        JazzScmCommand command = new JazzScmCommand( JazzConstants.CMD_DELIVER, repo, fileSet, getLogger() );
255
256        if ( repo.getWorkspace() != null && !repo.getWorkspace().equals( "" ) )
257        {
258            command.addArgument( JazzConstants.ARG_DELIVER_SOURCE );
259            command.addArgument( repo.getWorkspace() );
260        }
261
262        if ( repo.getFlowTarget() != null && !repo.getFlowTarget().equals( "" ) )
263        {
264            command.addArgument( JazzConstants.ARG_DELIVER_TARGET );
265            command.addArgument( repo.getFlowTarget() );
266        }
267
268        // This command is needed so that the deliver operation will work.
269        // Files that are not under source control (a--) [temp files etc]
270        // will cause the deliver operation to fail with the error:
271        // "Cannot deliver because there are one or more items that are not checked in.
272        // Check in the changes or rerun with --overwrite-uncommitted."
273        // However, from the maven perspective, we only need files that are
274        // under source control to be delivered. Maven has already checked
275        // for this (via the status command). 
276        //
277        // So we add this argument to allow the deliver to work.
278        command.addArgument( JazzConstants.ARG_OVERWRITE_UNCOMMITTED );
279
280        return command;
281    }
282}