001 package org.apache.maven.scm.provider.perforce;
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
023 import java.io.BufferedReader;
024 import java.io.File;
025 import java.io.IOException;
026 import java.io.InputStreamReader;
027 import java.net.InetAddress;
028 import java.net.UnknownHostException;
029
030 import org.apache.maven.scm.CommandParameters;
031 import org.apache.maven.scm.ScmException;
032 import org.apache.maven.scm.ScmFileSet;
033 import org.apache.maven.scm.command.add.AddScmResult;
034 import org.apache.maven.scm.command.blame.BlameScmResult;
035 import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
036 import org.apache.maven.scm.command.checkin.CheckInScmResult;
037 import org.apache.maven.scm.command.checkout.CheckOutScmResult;
038 import org.apache.maven.scm.command.diff.DiffScmResult;
039 import org.apache.maven.scm.command.edit.EditScmResult;
040 import org.apache.maven.scm.command.login.LoginScmResult;
041 import org.apache.maven.scm.command.remove.RemoveScmResult;
042 import org.apache.maven.scm.command.status.StatusScmResult;
043 import org.apache.maven.scm.command.tag.TagScmResult;
044 import org.apache.maven.scm.command.unedit.UnEditScmResult;
045 import org.apache.maven.scm.command.update.UpdateScmResult;
046 import org.apache.maven.scm.log.ScmLogger;
047 import org.apache.maven.scm.provider.AbstractScmProvider;
048 import org.apache.maven.scm.provider.ScmProviderRepository;
049 import org.apache.maven.scm.provider.perforce.command.PerforceInfoCommand;
050 import org.apache.maven.scm.provider.perforce.command.PerforceWhereCommand;
051 import org.apache.maven.scm.provider.perforce.command.add.PerforceAddCommand;
052 import org.apache.maven.scm.provider.perforce.command.blame.PerforceBlameCommand;
053 import org.apache.maven.scm.provider.perforce.command.changelog.PerforceChangeLogCommand;
054 import org.apache.maven.scm.provider.perforce.command.checkin.PerforceCheckInCommand;
055 import org.apache.maven.scm.provider.perforce.command.checkout.PerforceCheckOutCommand;
056 import org.apache.maven.scm.provider.perforce.command.diff.PerforceDiffCommand;
057 import org.apache.maven.scm.provider.perforce.command.edit.PerforceEditCommand;
058 import org.apache.maven.scm.provider.perforce.command.login.PerforceLoginCommand;
059 import org.apache.maven.scm.provider.perforce.command.remove.PerforceRemoveCommand;
060 import org.apache.maven.scm.provider.perforce.command.status.PerforceStatusCommand;
061 import org.apache.maven.scm.provider.perforce.command.tag.PerforceTagCommand;
062 import org.apache.maven.scm.provider.perforce.command.unedit.PerforceUnEditCommand;
063 import org.apache.maven.scm.provider.perforce.command.update.PerforceUpdateCommand;
064 import org.apache.maven.scm.provider.perforce.repository.PerforceScmProviderRepository;
065 import org.apache.maven.scm.repository.ScmRepositoryException;
066 import org.codehaus.plexus.util.StringUtils;
067 import org.codehaus.plexus.util.cli.Commandline;
068
069 /**
070 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl </a>
071 * @author mperham
072 *
073 * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="perforce"
074 */
075 public class PerforceScmProvider
076 extends AbstractScmProvider
077 {
078 // ----------------------------------------------------------------------
079 // ScmProvider Implementation
080 // ----------------------------------------------------------------------
081
082 public boolean requiresEditMode()
083 {
084 return true;
085 }
086
087 public ScmProviderRepository makeProviderScmRepository( String scmSpecificUrl, char delimiter )
088 throws ScmRepositoryException
089 {
090 String path;
091 int port = 0;
092 String host = null;
093
094 int i1 = scmSpecificUrl.indexOf( delimiter );
095 int i2 = scmSpecificUrl.indexOf( delimiter, i1 + 1 );
096
097 if ( i1 > 0 )
098 {
099 int lastDelimiter = scmSpecificUrl.lastIndexOf( delimiter );
100 path = scmSpecificUrl.substring( lastDelimiter + 1 );
101 host = scmSpecificUrl.substring( 0, i1 );
102
103 // If there is tree parts in the scm url, the second is the port
104 if ( i2 >= 0 )
105 {
106 try
107 {
108 String tmp = scmSpecificUrl.substring( i1 + 1, lastDelimiter );
109 port = Integer.parseInt( tmp );
110 }
111 catch ( NumberFormatException ex )
112 {
113 throw new ScmRepositoryException( "The port has to be a number." );
114 }
115 }
116 }
117 else
118 {
119 path = scmSpecificUrl;
120 }
121
122 String user = null;
123 String password = null;
124 if ( host != null && host.indexOf( '@' ) > 1 )
125 {
126 user = host.substring( 0, host.indexOf( '@' ) );
127 host = host.substring( host.indexOf( '@' ) + 1 );
128 }
129
130 if ( path.indexOf( '@' ) > 1 )
131 {
132 if ( host != null )
133 {
134 if ( getLogger().isWarnEnabled() )
135 {
136 getLogger().warn(
137 "Username as part of path is deprecated, the new format is "
138 + "scm:perforce:[username@]host:port:path_to_repository" );
139 }
140 }
141
142 user = path.substring( 0, path.indexOf( '@' ) );
143 path = path.substring( path.indexOf( '@' ) + 1 );
144 }
145
146 return new PerforceScmProviderRepository( host, port, path, user, password );
147 }
148
149 public String getScmType()
150 {
151 return "perforce";
152 }
153
154 /** {@inheritDoc} */
155 protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
156 CommandParameters parameters )
157 throws ScmException
158 {
159 PerforceChangeLogCommand command = new PerforceChangeLogCommand();
160 command.setLogger( getLogger() );
161 return (ChangeLogScmResult) command.execute( repository, fileSet, parameters );
162 }
163
164 public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
165 throws ScmException
166 {
167 PerforceAddCommand command = new PerforceAddCommand();
168 command.setLogger( getLogger() );
169 return (AddScmResult) command.execute( repository, fileSet, params );
170 }
171
172 protected RemoveScmResult remove( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
173 throws ScmException
174 {
175 PerforceRemoveCommand command = new PerforceRemoveCommand();
176 command.setLogger( getLogger() );
177 return (RemoveScmResult) command.execute( repository, fileSet, params );
178 }
179
180 protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
181 throws ScmException
182 {
183 PerforceCheckInCommand command = new PerforceCheckInCommand();
184 command.setLogger( getLogger() );
185 return (CheckInScmResult) command.execute( repository, fileSet, params );
186 }
187
188 protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
189 CommandParameters params )
190 throws ScmException
191 {
192 PerforceCheckOutCommand command = new PerforceCheckOutCommand();
193 command.setLogger( getLogger() );
194 return (CheckOutScmResult) command.execute( repository, fileSet, params );
195 }
196
197 protected DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
198 throws ScmException
199 {
200 PerforceDiffCommand command = new PerforceDiffCommand();
201 command.setLogger( getLogger() );
202 return (DiffScmResult) command.execute( repository, fileSet, params );
203 }
204
205 protected EditScmResult edit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
206 throws ScmException
207 {
208 PerforceEditCommand command = new PerforceEditCommand();
209 command.setLogger( getLogger() );
210 return (EditScmResult) command.execute( repository, fileSet, params );
211 }
212
213 protected LoginScmResult login( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
214 throws ScmException
215 {
216 PerforceLoginCommand command = new PerforceLoginCommand();
217 command.setLogger( getLogger() );
218 return (LoginScmResult) command.execute( repository, fileSet, params );
219 }
220
221 protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
222 throws ScmException
223 {
224 PerforceStatusCommand command = new PerforceStatusCommand();
225 command.setLogger( getLogger() );
226 return (StatusScmResult) command.execute( repository, fileSet, params );
227 }
228
229 protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
230 throws ScmException
231 {
232 PerforceTagCommand command = new PerforceTagCommand();
233 command.setLogger( getLogger() );
234 return (TagScmResult) command.execute( repository, fileSet, params );
235 }
236
237 protected UnEditScmResult unedit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
238 throws ScmException
239 {
240 PerforceUnEditCommand command = new PerforceUnEditCommand();
241 command.setLogger( getLogger() );
242 return (UnEditScmResult) command.execute( repository, fileSet, params );
243 }
244
245 protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
246 throws ScmException
247 {
248 PerforceUpdateCommand command = new PerforceUpdateCommand();
249 command.setLogger( getLogger() );
250 return (UpdateScmResult) command.execute( repository, fileSet, params );
251 }
252
253 protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters params )
254 throws ScmException
255 {
256 PerforceBlameCommand command = new PerforceBlameCommand();
257 command.setLogger( getLogger() );
258 return (BlameScmResult) command.execute( repository, fileSet, params );
259 }
260
261 public static Commandline createP4Command( PerforceScmProviderRepository repo, File workingDir )
262 {
263 Commandline command = new Commandline();
264 command.setExecutable( "p4" );
265 if ( workingDir != null )
266 {
267 // SCM-209
268 command.createArg().setValue( "-d" );
269 command.createArg().setValue( workingDir.getAbsolutePath() );
270 }
271
272 if ( repo.getHost() != null )
273 {
274 command.createArg().setValue( "-p" );
275 String value = repo.getHost();
276 if ( repo.getPort() != 0 )
277 {
278 value += ":" + Integer.toString( repo.getPort() );
279 }
280 command.createArg().setValue( value );
281 }
282
283 if ( StringUtils.isNotEmpty( repo.getUser() ) )
284 {
285 command.createArg().setValue( "-u" );
286 command.createArg().setValue( repo.getUser() );
287 }
288
289 if ( StringUtils.isNotEmpty( repo.getPassword() ) )
290 {
291 command.createArg().setValue( "-P" );
292 command.createArg().setValue( repo.getPassword() );
293 }
294 return command;
295 }
296
297 public static String clean( String string )
298 {
299 if ( string.indexOf( " -P " ) == -1 )
300 {
301 return string;
302 }
303 int idx = string.indexOf( " -P " ) + 4;
304 int end = string.indexOf( ' ', idx );
305 return string.substring( 0, idx ) + StringUtils.repeat( "*", end - idx ) + string.substring( end );
306 }
307
308 /**
309 * Given a path like "//depot/foo/bar", returns the
310 * proper path to include everything beneath it.
311 * <p/>
312 * //depot/foo/bar -> //depot/foo/bar/...
313 * //depot/foo/bar/ -> //depot/foo/bar/...
314 * //depot/foo/bar/... -> //depot/foo/bar/...
315 *
316 * @param repoPath
317 * @return
318 */
319 public static String getCanonicalRepoPath( String repoPath )
320 {
321 if ( repoPath.endsWith( "/..." ) )
322 {
323 return repoPath;
324 }
325 else if ( repoPath.endsWith( "/" ) )
326 {
327 return repoPath + "...";
328 }
329 else
330 {
331 return repoPath + "/...";
332 }
333 }
334
335 private static final String NEWLINE = "\r\n";
336
337 /*
338 * Clientspec name can be overridden with the system property below. I don't
339 * know of any way for this code to get access to maven's settings.xml so this
340 * is the best I can do.
341 *
342 * Sample clientspec:
343
344 Client: mperham-mikeperham-dt-maven
345 Root: d:\temp\target
346 Owner: mperham
347 View:
348 //depot/sandbox/mperham/tsa/tsa-domain/... //mperham-mikeperham-dt-maven/...
349 Description:
350 Created by maven-scm-provider-perforce
351
352 */
353 public static String createClientspec( ScmLogger logger, PerforceScmProviderRepository repo, File workDir,
354 String repoPath )
355 {
356 String clientspecName = getClientspecName( logger, repo, workDir );
357 String userName = getUsername( logger, repo );
358
359 String rootDir;
360 try
361 {
362 // SCM-184
363 rootDir = workDir.getCanonicalPath();
364 }
365 catch ( IOException ex )
366 {
367 //getLogger().error("Error getting canonical path for working directory: " + workDir, ex);
368 rootDir = workDir.getAbsolutePath();
369 }
370
371 StringBuilder buf = new StringBuilder();
372 buf.append( "Client: " ).append( clientspecName ).append( NEWLINE );
373 buf.append( "Root: " ).append( rootDir ).append( NEWLINE );
374 buf.append( "Owner: " ).append( userName ).append( NEWLINE );
375 buf.append( "View:" ).append( NEWLINE );
376 buf.append( "\t" ).append( PerforceScmProvider.getCanonicalRepoPath( repoPath ) );
377 buf.append( " //" ).append( clientspecName ).append( "/..." ).append( NEWLINE );
378 buf.append( "Description:" ).append( NEWLINE );
379 buf.append( "\t" ).append( "Created by maven-scm-provider-perforce" ).append( NEWLINE );
380 return buf.toString();
381 }
382
383 public static final String DEFAULT_CLIENTSPEC_PROPERTY = "maven.scm.perforce.clientspec.name";
384
385 public static String getClientspecName( ScmLogger logger, PerforceScmProviderRepository repo, File workDir )
386 {
387 String def = generateDefaultClientspecName( logger, repo, workDir );
388 // until someone put clearProperty in DefaultContinuumScm.getScmRepository( Project , boolean )
389 String l = System.getProperty( DEFAULT_CLIENTSPEC_PROPERTY, def );
390 if ( l == null || "".equals( l.trim() ) )
391 {
392 return def;
393 }
394 return l;
395 }
396
397 private static String generateDefaultClientspecName( ScmLogger logger, PerforceScmProviderRepository repo,
398 File workDir )
399 {
400 String username = getUsername( logger, repo );
401 String hostname;
402 String path;
403 try
404 {
405 hostname = InetAddress.getLocalHost().getHostName();
406 // [SCM-370][SCM-351] client specs cannot contain forward slashes, spaces and ~; "-" is okay
407 path = workDir.getCanonicalPath().replaceAll( "[/ ~]", "-" );
408 }
409 catch ( UnknownHostException e )
410 {
411 // Should never happen
412 throw new RuntimeException( e );
413 }
414 catch ( IOException e )
415 {
416 throw new RuntimeException( e );
417 }
418 return username + "-" + hostname + "-MavenSCM-" + path;
419 }
420
421 private static String getUsername( ScmLogger logger, PerforceScmProviderRepository repo )
422 {
423 String username = PerforceInfoCommand.getInfo( logger, repo ).getEntry( "User name" );
424 if ( username == null )
425 {
426 // os user != perforce user
427 username = repo.getUser();
428 if ( username == null )
429 {
430 username = System.getProperty( "user.name", "nouser" );
431 }
432 }
433 return username;
434 }
435
436 /**
437 * This is a "safe" method which handles cases where repo.getPath() is
438 * not actually a valid Perforce depot location. This is a frequent error
439 * due to branches and directory naming where dir name != artifactId.
440 *
441 * @param log the logging object to use
442 * @param repo the Perforce repo
443 * @param basedir the base directory we are operating in. If pom.xml exists in this directory,
444 * this method will verify <pre>repo.getPath()/pom.xml</pre> == <pre>p4 where basedir/pom.xml</pre>
445 * @return repo.getPath if it is determined to be accurate. The p4 where location otherwise.
446 */
447 public static String getRepoPath( ScmLogger log, PerforceScmProviderRepository repo, File basedir )
448 {
449 PerforceWhereCommand where = new PerforceWhereCommand( log, repo );
450
451 // Handle an edge case where we release:prepare'd a module with an invalid SCM location.
452 // In this case, the release.properties will contain the invalid URL for checkout purposes
453 // during release:perform. In this case, the basedir is not the module root so we detect that
454 // and remove the trailing target/checkout directory.
455 if ( basedir.toString().replace( '\\', '/' ).endsWith( "/target/checkout" ) )
456 {
457 String dir = basedir.toString();
458 basedir = new File( dir.substring( 0, dir.length() - "/target/checkout".length() ) );
459 log.debug( "Fixing checkout URL: " + basedir );
460 }
461 File pom = new File( basedir, "pom.xml" );
462 String loc = repo.getPath();
463 log.debug( "SCM path in pom: " + loc );
464 if ( pom.exists() )
465 {
466 loc = where.getDepotLocation( pom );
467 if ( loc == null )
468 {
469 loc = repo.getPath();
470 log.debug( "cannot find depot => using " + loc );
471 }
472 else if ( loc.endsWith( "/pom.xml" ) )
473 {
474 loc = loc.substring( 0, loc.length() - "/pom.xml".length() );
475 log.debug( "Actual POM location: " + loc );
476 if ( !repo.getPath().equals( loc ) )
477 {
478 log.info( "The SCM location in your pom.xml (" + repo.getPath()
479 + ") is not equal to the depot location (" + loc
480 + "). This happens frequently with branches. " + "Ignoring the SCM location." );
481 }
482 }
483 }
484 return loc;
485 }
486
487
488 private static Boolean live = null;
489
490 public static boolean isLive()
491 {
492 if ( live == null )
493 {
494 if ( !Boolean.getBoolean( "maven.scm.testing" ) )
495 {
496 // We are not executing in the tests so we are live.
497 live = Boolean.TRUE;
498 }
499 else
500 {
501 // During unit tests, we need to check the local system
502 // to see if the user has Perforce installed. If not, we mark
503 // the provider as "not live" (or dead, I suppose!) and skip
504 // anything that requires an active server connection.
505 try
506 {
507 Commandline command = new Commandline();
508 command.setExecutable( "p4" );
509 Process proc = command.execute();
510 BufferedReader br = new BufferedReader( new InputStreamReader( proc.getInputStream() ) );
511 @SuppressWarnings( "unused" )
512 String line;
513 while ( ( line = br.readLine() ) != null )
514 {
515 //System.out.println(line);
516 }
517 int rc = proc.exitValue();
518 live = ( rc == 0 ? Boolean.TRUE : Boolean.FALSE );
519 }
520 catch ( Exception e )
521 {
522 e.printStackTrace();
523 live = Boolean.FALSE;
524 }
525 }
526 }
527
528 return live.booleanValue();
529 }
530 }