001    package org.apache.maven.scm.provider.perforce.command.checkout;
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 org.apache.maven.scm.ScmException;
023    import org.apache.maven.scm.ScmFileSet;
024    import org.apache.maven.scm.ScmVersion;
025    import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
026    import org.apache.maven.scm.command.checkout.CheckOutScmResult;
027    import org.apache.maven.scm.provider.ScmProviderRepository;
028    import org.apache.maven.scm.provider.perforce.PerforceScmProvider;
029    import org.apache.maven.scm.provider.perforce.command.PerforceCommand;
030    import org.apache.maven.scm.provider.perforce.repository.PerforceScmProviderRepository;
031    import org.apache.regexp.RE;
032    import org.codehaus.plexus.util.IOUtil;
033    import org.codehaus.plexus.util.StringUtils;
034    import org.codehaus.plexus.util.cli.CommandLineException;
035    import org.codehaus.plexus.util.cli.CommandLineUtils;
036    import org.codehaus.plexus.util.cli.Commandline;
037    
038    import java.io.BufferedReader;
039    import java.io.ByteArrayInputStream;
040    import java.io.File;
041    import java.io.IOException;
042    import java.io.InputStreamReader;
043    
044    /**
045     * @author Mike Perham
046     * @version $Id: PerforceCheckOutCommand.java 1306867 2012-03-29 13:45:10Z olamy $
047     */
048    public class PerforceCheckOutCommand
049        extends AbstractCheckOutCommand
050        implements PerforceCommand
051    {
052        private String actualLocation;
053    
054        /**
055         * Check out the depot code at <code>repo.getPath()</code> into the target
056         * directory at <code>files.getBasedir</code>. Perforce does not support
057         * arbitrary checkout of versioned source so we need to set up a well-known
058         * clientspec which will hold the required info.
059         * <p/>
060         * 1) A clientspec will be created or updated which holds a temporary
061         * mapping from the repo path to the target directory.
062         * 2) This clientspec is sync'd to pull all the files onto the client
063         * <p/>
064         * {@inheritDoc}
065         */
066        protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet files,
067                                                            ScmVersion version, boolean recursive )
068            throws ScmException
069        {
070            PerforceScmProviderRepository prepo = (PerforceScmProviderRepository) repo;
071            File workingDirectory = new File( files.getBasedir().getAbsolutePath() );
072    
073            actualLocation = PerforceScmProvider.getRepoPath( getLogger(), prepo, files.getBasedir() );
074    
075            String specname = PerforceScmProvider.getClientspecName( getLogger(), prepo, workingDirectory );
076            PerforceCheckOutConsumer consumer = new PerforceCheckOutConsumer( specname, actualLocation );
077            if ( getLogger().isInfoEnabled() )
078            {
079                getLogger().info( "Checkout working directory: " + workingDirectory );
080            }
081            Commandline cl = null;
082    
083            // Create or update a clientspec so we can checkout the code to a particular location
084            try
085            {
086                // Ahhh, glorious Perforce.  Create and update of clientspecs is the exact
087                // same operation so we don't need to distinguish between the two modes.
088                cl = PerforceScmProvider.createP4Command( prepo, workingDirectory );
089                cl.createArg().setValue( "client" );
090                cl.createArg().setValue( "-i" );
091                if ( getLogger().isInfoEnabled() )
092                {
093                    getLogger().info( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
094                }
095    
096                String client =
097                    PerforceScmProvider.createClientspec( getLogger(), prepo, workingDirectory, actualLocation );
098    
099                if ( getLogger().isDebugEnabled() )
100                {
101                    getLogger().debug( "Updating clientspec:\n" + client );
102                }
103    
104                CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
105                int exitCode =
106                    CommandLineUtils.executeCommandLine( cl, new ByteArrayInputStream( client.getBytes() ), consumer, err );
107    
108                if ( exitCode != 0 )
109                {
110                    String cmdLine = CommandLineUtils.toString( cl.getCommandline() );
111    
112                    StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
113                    msg.append( '\n' );
114                    msg.append( "Command line was:" + cmdLine );
115    
116                    throw new CommandLineException( msg.toString() );
117                }
118            }
119            catch ( CommandLineException e )
120            {
121                if ( getLogger().isErrorEnabled() )
122                {
123                    getLogger().error( "CommandLineException " + e.getMessage(), e );
124                }
125            }
126    
127            boolean clientspecExists = consumer.isSuccess();
128    
129            // Perform the actual checkout using that clientspec
130            try
131            {
132                if ( clientspecExists )
133                {
134                    try
135                    {
136                        getLastChangelist( prepo, workingDirectory, specname );
137                        cl = createCommandLine( prepo, workingDirectory, version, specname );
138                        if ( getLogger().isDebugEnabled() )
139                        {
140                            getLogger().debug( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
141                        }
142                        Process proc = cl.execute();
143                        BufferedReader br = new BufferedReader( new InputStreamReader( proc.getInputStream() ) );
144                        String line;
145                        while ( ( line = br.readLine() ) != null )
146                        {
147                            if ( getLogger().isDebugEnabled() )
148                            {
149                                getLogger().debug( "Consuming: " + line );
150                            }
151                            consumer.consumeLine( line );
152                        }
153                        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
154                        int exitCode = CommandLineUtils.executeCommandLine( cl, consumer, err );
155    
156                        if ( exitCode != 0 )
157                        {
158                            String cmdLine = CommandLineUtils.toString( cl.getCommandline() );
159    
160                            StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
161                            msg.append( '\n' );
162                            msg.append( "Command line was:" + cmdLine );
163    
164                            throw new CommandLineException( msg.toString() );
165                        }
166                        if ( getLogger().isDebugEnabled() )
167                        {
168                            getLogger().debug( "Perforce sync complete." );
169                        }
170                    }
171                    catch ( CommandLineException e )
172                    {
173                        if ( getLogger().isErrorEnabled() )
174                        {
175                            getLogger().error( "CommandLineException " + e.getMessage(), e );
176                        }
177                    }
178                    catch ( IOException e )
179                    {
180                        if ( getLogger().isErrorEnabled() )
181                        {
182                            getLogger().error( "IOException " + e.getMessage(), e );
183                        }
184                    }
185                }
186    
187                if ( consumer.isSuccess() )
188                {
189                    return new CheckOutScmResult( cl.toString(), consumer.getCheckedout() );
190                }
191                else
192                {
193                    return new CheckOutScmResult( cl.toString(), "Unable to sync.  Are you logged in?",
194                                                  consumer.getOutput(), consumer.isSuccess() );
195                }
196            }
197            finally
198            {
199                // See SCM-113
200                // Support transient clientspecs as we don't want to create 1000s of permanent clientspecs
201                if ( clientspecExists && !prepo.isPersistCheckout() )
202                {
203                    // Delete the clientspec
204                    InputStreamReader isReader = null;
205                    InputStreamReader isReaderErr = null;
206                    try
207                    {
208                        cl = PerforceScmProvider.createP4Command( prepo, workingDirectory );
209                        cl.createArg().setValue( "client" );
210                        cl.createArg().setValue( "-d" );
211                        cl.createArg().setValue( specname );
212                        if ( getLogger().isInfoEnabled() )
213                        {
214                            getLogger().info( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
215                        }
216                        Process proc = cl.execute();
217                        isReader = new InputStreamReader( proc.getInputStream() );
218                        BufferedReader br = new BufferedReader( isReader );
219                        String line;
220                        while ( ( line = br.readLine() ) != null )
221                        {
222                            if ( getLogger().isDebugEnabled() )
223                            {
224                                getLogger().debug( "Consuming: " + line );
225                            }
226                            consumer.consumeLine( line );
227                        }
228                        br.close();
229                        // Read errors from STDERR
230                        isReaderErr = new InputStreamReader( proc.getErrorStream() );
231                        BufferedReader brErr = new BufferedReader( isReaderErr );
232                        while ( ( line = brErr.readLine() ) != null )
233                        {
234                            if ( getLogger().isDebugEnabled() )
235                            {
236                                getLogger().debug( "Consuming stderr: " + line );
237                            }
238                            consumer.consumeLine( line );
239                        }
240                        brErr.close();
241                    }
242                    catch ( CommandLineException e )
243                    {
244                        if ( getLogger().isErrorEnabled() )
245                        {
246                            getLogger().error( "CommandLineException " + e.getMessage(), e );
247                        }
248                    }
249                    catch ( IOException e )
250                    {
251                        if ( getLogger().isErrorEnabled() )
252                        {
253                            getLogger().error( "IOException " + e.getMessage(), e );
254                        }
255                    }
256                    finally
257                    {
258                        IOUtil.close( isReader );
259                        IOUtil.close( isReaderErr );
260                    }
261                }
262                else if ( clientspecExists )
263                {
264                    // SCM-165 Save clientspec in memory so we can reuse it with further commands in this VM.
265                    System.setProperty( PerforceScmProvider.DEFAULT_CLIENTSPEC_PROPERTY, specname );
266                }
267            }
268        }
269    
270        public static Commandline createCommandLine( PerforceScmProviderRepository repo, File workingDirectory,
271                                                     ScmVersion version, String specname )
272        {
273            Commandline command = PerforceScmProvider.createP4Command( repo, workingDirectory );
274    
275            command.createArg().setValue( "-c" + specname );
276            command.createArg().setValue( "sync" );
277    
278            // Use a simple heuristic to determine if we should use the Force flag
279            // on sync.  Forcing sync is a HUGE performance hit but is required in
280            // rare instances where source is somehow deleted.  If the target
281            // directory is completely empty, assume a force is required.  If
282            // not empty, we assume a previous checkout was already done and a normal
283            // sync will suffice.
284            // SCM-110
285            String[] files = workingDirectory.list();
286            if ( files == null || files.length == 0 )
287            {
288                // We need to force so checkout to an empty directory will work.
289                command.createArg().setValue( "-f" );
290            }
291    
292            // Not sure what to do here. I'm unclear whether we should be
293            // sync'ing each file individually to the label or just sync the
294            // entire contents of the workingDir. I'm going to assume the
295            // latter until the exact semantics are clearer.
296            if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
297            {
298                command.createArg().setValue( "@" + version.getName() );
299            }
300            return command;
301        }
302    
303        private int getLastChangelist( PerforceScmProviderRepository repo, File workingDirectory, String specname )
304        {
305            int lastChangelist = 0;
306            try
307            {
308                Commandline command = PerforceScmProvider.createP4Command( repo, workingDirectory );
309    
310                command.createArg().setValue( "-c" + specname );
311                command.createArg().setValue( "changes" );
312                command.createArg().setValue( "-m1" );
313                command.createArg().setValue( "-ssubmitted" );
314                command.createArg().setValue( "//" + specname + "/..." );
315                getLogger().debug( "Executing: " + PerforceScmProvider.clean( command.toString() ) );
316                Process proc = command.execute();
317                BufferedReader br = new BufferedReader( new InputStreamReader( proc.getInputStream() ) );
318                String line;
319    
320                String lastChangelistStr = "";
321                while ( ( line = br.readLine() ) != null )
322                {
323                    getLogger().debug( "Consuming: " + line );
324                    RE changeRegexp = new RE( "Change (\\d+)" );
325                    if ( changeRegexp.match( line ) )
326                    {
327                        lastChangelistStr = changeRegexp.getParen( 1 );
328                    }
329                }
330                br.close();
331                // TODO: Read errors from STDERR?
332    
333                try
334                {
335                    lastChangelist = Integer.parseInt( lastChangelistStr );
336                }
337                catch ( NumberFormatException nfe )
338                {
339                    getLogger().debug( "Could not parse changelist from line " + line );
340                }
341            }
342            catch ( IOException e )
343            {
344                getLogger().error( e );
345            }
346            catch ( CommandLineException e )
347            {
348                getLogger().error( e );
349            }
350    
351            return lastChangelist;
352        }
353    }