View Javadoc
1   package org.apache.maven.scm.provider.perforce.command.checkout;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.scm.ScmException;
23  import org.apache.maven.scm.ScmFileSet;
24  import org.apache.maven.scm.ScmVersion;
25  import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
26  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
27  import org.apache.maven.scm.provider.ScmProviderRepository;
28  import org.apache.maven.scm.provider.perforce.PerforceScmProvider;
29  import org.apache.maven.scm.provider.perforce.command.PerforceCommand;
30  import org.apache.maven.scm.provider.perforce.repository.PerforceScmProviderRepository;
31  import org.codehaus.plexus.util.IOUtil;
32  import org.codehaus.plexus.util.StringUtils;
33  import org.codehaus.plexus.util.cli.CommandLineException;
34  import org.codehaus.plexus.util.cli.CommandLineUtils;
35  import org.codehaus.plexus.util.cli.Commandline;
36  
37  import java.io.BufferedReader;
38  import java.io.ByteArrayInputStream;
39  import java.io.File;
40  import java.io.IOException;
41  import java.io.InputStreamReader;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  /**
46   * @author Mike Perham
47   *
48   */
49  public class PerforceCheckOutCommand
50      extends AbstractCheckOutCommand
51      implements PerforceCommand
52  {
53      private String actualLocation;
54  
55      /**
56       * Check out the depot code at <code>repo.getPath()</code> into the target
57       * directory at <code>files.getBasedir</code>. Perforce does not support
58       * arbitrary checkout of versioned source so we need to set up a well-known
59       * clientspec which will hold the required info.
60       * <p/>
61       * 1) A clientspec will be created or updated which holds a temporary
62       * mapping from the repo path to the target directory.
63       * 2) This clientspec is sync'd to pull all the files onto the client
64       * <p/>
65       * {@inheritDoc}
66       */
67      protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet files,
68                                                          ScmVersion version, boolean recursive )
69          throws ScmException
70      {
71          PerforceScmProviderRepository prepo = (PerforceScmProviderRepository) repo;
72          File workingDirectory = new File( files.getBasedir().getAbsolutePath() );
73  
74          actualLocation = PerforceScmProvider.getRepoPath( getLogger(), prepo, files.getBasedir() );
75  
76          String specname = PerforceScmProvider.getClientspecName( getLogger(), prepo, workingDirectory );
77          PerforceCheckOutConsumer consumer = new PerforceCheckOutConsumer( specname, actualLocation );
78          if ( getLogger().isInfoEnabled() )
79          {
80              getLogger().info( "Checkout working directory: " + workingDirectory );
81          }
82          Commandline cl = null;
83  
84          // Create or update a clientspec so we can checkout the code to a particular location
85          try
86          {
87              // Ahhh, glorious Perforce.  Create and update of clientspecs is the exact
88              // same operation so we don't need to distinguish between the two modes.
89              cl = PerforceScmProvider.createP4Command( prepo, workingDirectory );
90              cl.createArg().setValue( "client" );
91              cl.createArg().setValue( "-i" );
92              if ( getLogger().isInfoEnabled() )
93              {
94                  getLogger().info( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
95              }
96  
97              String client =
98                  PerforceScmProvider.createClientspec( getLogger(), prepo, workingDirectory, actualLocation );
99  
100             if ( getLogger().isDebugEnabled() )
101             {
102                 getLogger().debug( "Updating clientspec:\n" + client );
103             }
104 
105             CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
106             int exitCode =
107                 CommandLineUtils.executeCommandLine( cl, new ByteArrayInputStream( client.getBytes() ), consumer, err );
108 
109             if ( exitCode != 0 )
110             {
111                 String cmdLine = CommandLineUtils.toString( cl.getCommandline() );
112 
113                 StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
114                 msg.append( '\n' );
115                 msg.append( "Command line was:" + cmdLine );
116 
117                 throw new CommandLineException( msg.toString() );
118             }
119         }
120         catch ( CommandLineException e )
121         {
122             if ( getLogger().isErrorEnabled() )
123             {
124                 getLogger().error( "CommandLineException " + e.getMessage(), e );
125             }
126         }
127 
128         boolean clientspecExists = consumer.isSuccess();
129 
130         // Perform the actual checkout using that clientspec
131         try
132         {
133             if ( clientspecExists )
134             {
135                 try
136                 {
137                     getLastChangelist( prepo, workingDirectory, specname );
138                     cl = createCommandLine( prepo, workingDirectory, version, specname );
139                     if ( getLogger().isDebugEnabled() )
140                     {
141                         getLogger().debug( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
142                     }
143                     Process proc = cl.execute();
144                     BufferedReader br = new BufferedReader( new InputStreamReader( proc.getInputStream() ) );
145                     String line;
146                     while ( ( line = br.readLine() ) != null )
147                     {
148                         if ( getLogger().isDebugEnabled() )
149                         {
150                             getLogger().debug( "Consuming: " + line );
151                         }
152                         consumer.consumeLine( line );
153                     }
154                     CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
155                     int exitCode = CommandLineUtils.executeCommandLine( cl, consumer, err );
156 
157                     if ( exitCode != 0 )
158                     {
159                         String cmdLine = CommandLineUtils.toString( cl.getCommandline() );
160 
161                         StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
162                         msg.append( '\n' );
163                         msg.append( "Command line was:" + cmdLine );
164 
165                         throw new CommandLineException( msg.toString() );
166                     }
167                     if ( getLogger().isDebugEnabled() )
168                     {
169                         getLogger().debug( "Perforce sync complete." );
170                     }
171                 }
172                 catch ( CommandLineException e )
173                 {
174                     if ( getLogger().isErrorEnabled() )
175                     {
176                         getLogger().error( "CommandLineException " + e.getMessage(), e );
177                     }
178                 }
179                 catch ( IOException e )
180                 {
181                     if ( getLogger().isErrorEnabled() )
182                     {
183                         getLogger().error( "IOException " + e.getMessage(), e );
184                     }
185                 }
186             }
187 
188             if ( consumer.isSuccess() )
189             {
190                 return new CheckOutScmResult( cl.toString(), consumer.getCheckedout() );
191             }
192             else
193             {
194                 return new CheckOutScmResult( cl.toString(), "Unable to sync.  Are you logged in?",
195                                               consumer.getOutput(), consumer.isSuccess() );
196             }
197         }
198         finally
199         {
200             // See SCM-113
201             // Support transient clientspecs as we don't want to create 1000s of permanent clientspecs
202             if ( clientspecExists && !prepo.isPersistCheckout() )
203             {
204                 // Delete the clientspec
205                 InputStreamReader isReader = null;
206                 InputStreamReader isReaderErr = null;
207                 try
208                 {
209                     cl = PerforceScmProvider.createP4Command( prepo, workingDirectory );
210                     cl.createArg().setValue( "client" );
211                     cl.createArg().setValue( "-d" );
212                     cl.createArg().setValue( specname );
213                     if ( getLogger().isInfoEnabled() )
214                     {
215                         getLogger().info( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
216                     }
217                     Process proc = cl.execute();
218                     isReader = new InputStreamReader( proc.getInputStream() );
219                     BufferedReader br = new BufferedReader( isReader );
220                     String line;
221                     while ( ( line = br.readLine() ) != null )
222                     {
223                         if ( getLogger().isDebugEnabled() )
224                         {
225                             getLogger().debug( "Consuming: " + line );
226                         }
227                         consumer.consumeLine( line );
228                     }
229                     br.close();
230                     // Read errors from STDERR
231                     isReaderErr = new InputStreamReader( proc.getErrorStream() );
232                     BufferedReader brErr = new BufferedReader( isReaderErr );
233                     while ( ( line = brErr.readLine() ) != null )
234                     {
235                         if ( getLogger().isDebugEnabled() )
236                         {
237                             getLogger().debug( "Consuming stderr: " + line );
238                         }
239                         consumer.consumeLine( line );
240                     }
241                     brErr.close();
242                 }
243                 catch ( CommandLineException e )
244                 {
245                     if ( getLogger().isErrorEnabled() )
246                     {
247                         getLogger().error( "CommandLineException " + e.getMessage(), e );
248                     }
249                 }
250                 catch ( IOException e )
251                 {
252                     if ( getLogger().isErrorEnabled() )
253                     {
254                         getLogger().error( "IOException " + e.getMessage(), e );
255                     }
256                 }
257                 finally
258                 {
259                     IOUtil.close( isReader );
260                     IOUtil.close( isReaderErr );
261                 }
262             }
263             else if ( clientspecExists )
264             {
265                 // SCM-165 Save clientspec in memory so we can reuse it with further commands in this VM.
266                 System.setProperty( PerforceScmProvider.DEFAULT_CLIENTSPEC_PROPERTY, specname );
267             }
268         }
269     }
270 
271     public static Commandline createCommandLine( PerforceScmProviderRepository repo, File workingDirectory,
272                                                  ScmVersion version, String specname )
273     {
274         Commandline command = PerforceScmProvider.createP4Command( repo, workingDirectory );
275 
276         command.createArg().setValue( "-c" + specname );
277         command.createArg().setValue( "sync" );
278 
279         // Use a simple heuristic to determine if we should use the Force flag
280         // on sync.  Forcing sync is a HUGE performance hit but is required in
281         // rare instances where source is somehow deleted.  If the target
282         // directory is completely empty, assume a force is required.  If
283         // not empty, we assume a previous checkout was already done and a normal
284         // sync will suffice.
285         // SCM-110
286         String[] files = workingDirectory.list();
287         if ( files == null || files.length == 0 )
288         {
289             // We need to force so checkout to an empty directory will work.
290             command.createArg().setValue( "-f" );
291         }
292 
293         // Not sure what to do here. I'm unclear whether we should be
294         // sync'ing each file individually to the label or just sync the
295         // entire contents of the workingDir. I'm going to assume the
296         // latter until the exact semantics are clearer.
297         if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
298         {
299             command.createArg().setValue( "@" + version.getName() );
300         }
301         return command;
302     }
303 
304     private int getLastChangelist( PerforceScmProviderRepository repo, File workingDirectory, String specname )
305     {
306         int lastChangelist = 0;
307         try
308         {
309             Commandline command = PerforceScmProvider.createP4Command( repo, workingDirectory );
310 
311             command.createArg().setValue( "-c" + specname );
312             command.createArg().setValue( "changes" );
313             command.createArg().setValue( "-m1" );
314             command.createArg().setValue( "-ssubmitted" );
315             command.createArg().setValue( "//" + specname + "/..." );
316             getLogger().debug( "Executing: " + PerforceScmProvider.clean( command.toString() ) );
317             Process proc = command.execute();
318             BufferedReader br = new BufferedReader( new InputStreamReader( proc.getInputStream() ) );
319             String line;
320 
321             String lastChangelistStr = "";
322             while ( ( line = br.readLine() ) != null )
323             {
324                 getLogger().debug( "Consuming: " + line );
325                 Pattern changeRegexp = Pattern.compile( "Change (\\d+)" );
326                 Matcher matcher = changeRegexp.matcher( line );
327                 if ( matcher.find() )
328                 {
329                     lastChangelistStr = matcher.group( 1 );
330                 }
331             }
332             br.close();
333             // TODO: Read errors from STDERR?
334 
335             try
336             {
337                 lastChangelist = Integer.parseInt( lastChangelistStr );
338             }
339             catch ( NumberFormatException nfe )
340             {
341                 getLogger().debug( "Could not parse changelist from line " + line );
342             }
343         }
344         catch ( IOException e )
345         {
346             getLogger().error( e );
347         }
348         catch ( CommandLineException e )
349         {
350             getLogger().error( e );
351         }
352 
353         return lastChangelist;
354     }
355 }