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 * @version $Id: PerforceScmProvider.java 1306867 2012-03-29 13:45:10Z olamy $ 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 }