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.apache.regexp.RE;
32  import org.codehaus.plexus.util.IOUtil;
33  import org.codehaus.plexus.util.StringUtils;
34  import org.codehaus.plexus.util.cli.CommandLineException;
35  import org.codehaus.plexus.util.cli.CommandLineUtils;
36  import org.codehaus.plexus.util.cli.Commandline;
37  
38  import java.io.BufferedReader;
39  import java.io.ByteArrayInputStream;
40  import java.io.File;
41  import java.io.IOException;
42  import java.io.InputStreamReader;
43  
44  /**
45   * @author Mike Perham
46   *
47   */
48  public class PerforceCheckOutCommand
49      extends AbstractCheckOutCommand
50      implements PerforceCommand
51  {
52      private String actualLocation;
53  
54      /**
55       * Check out the depot code at <code>repo.getPath()</code> into the target
56       * directory at <code>files.getBasedir</code>. Perforce does not support
57       * arbitrary checkout of versioned source so we need to set up a well-known
58       * clientspec which will hold the required info.
59       * <p/>
60       * 1) A clientspec will be created or updated which holds a temporary
61       * mapping from the repo path to the target directory.
62       * 2) This clientspec is sync'd to pull all the files onto the client
63       * <p/>
64       * {@inheritDoc}
65       */
66      protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet files,
67                                                          ScmVersion version, boolean recursive )
68          throws ScmException
69      {
70          PerforceScmProviderRepository prepo = (PerforceScmProviderRepository) repo;
71          File workingDirectory = new File( files.getBasedir().getAbsolutePath() );
72  
73          actualLocation = PerforceScmProvider.getRepoPath( getLogger(), prepo, files.getBasedir() );
74  
75          String specname = PerforceScmProvider.getClientspecName( getLogger(), prepo, workingDirectory );
76          PerforceCheckOutConsumer consumer = new PerforceCheckOutConsumer( specname, actualLocation );
77          if ( getLogger().isInfoEnabled() )
78          {
79              getLogger().info( "Checkout working directory: " + workingDirectory );
80          }
81          Commandline cl = null;
82  
83          // Create or update a clientspec so we can checkout the code to a particular location
84          try
85          {
86              // Ahhh, glorious Perforce.  Create and update of clientspecs is the exact
87              // same operation so we don't need to distinguish between the two modes.
88              cl = PerforceScmProvider.createP4Command( prepo, workingDirectory );
89              cl.createArg().setValue( "client" );
90              cl.createArg().setValue( "-i" );
91              if ( getLogger().isInfoEnabled() )
92              {
93                  getLogger().info( "Executing: " + PerforceScmProvider.clean( cl.toString() ) );
94              }
95  
96              String client =
97                  PerforceScmProvider.createClientspec( getLogger(), prepo, workingDirectory, actualLocation );
98  
99              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 }