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 *
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 }