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