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