001 package org.apache.maven.scm.provider.accurev.cli; 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 java.io.ByteArrayInputStream; 023 import java.io.File; 024 import java.io.InputStream; 025 import java.util.ArrayList; 026 import java.util.Collection; 027 import java.util.Collections; 028 import java.util.HashMap; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.regex.Pattern; 032 033 import org.apache.maven.scm.command.blame.BlameLine; 034 import org.apache.maven.scm.log.ScmLogger; 035 import org.apache.maven.scm.provider.accurev.AccuRev; 036 import org.apache.maven.scm.provider.accurev.AccuRevException; 037 import org.apache.maven.scm.provider.accurev.AccuRevInfo; 038 import org.apache.maven.scm.provider.accurev.AccuRevStat; 039 import org.apache.maven.scm.provider.accurev.AccuRevVersion; 040 import org.apache.maven.scm.provider.accurev.CategorisedElements; 041 import org.apache.maven.scm.provider.accurev.FileDifference; 042 import org.apache.maven.scm.provider.accurev.Stream; 043 import org.apache.maven.scm.provider.accurev.Transaction; 044 import org.apache.maven.scm.provider.accurev.WorkSpace; 045 import org.codehaus.plexus.util.Os; 046 import org.codehaus.plexus.util.StringUtils; 047 import org.codehaus.plexus.util.cli.CommandLineException; 048 import org.codehaus.plexus.util.cli.CommandLineUtils; 049 import org.codehaus.plexus.util.cli.Commandline; 050 import org.codehaus.plexus.util.cli.StreamConsumer; 051 052 public class AccuRevCommandLine 053 implements AccuRev 054 { 055 056 private static final String[] EMPTY_STRING_ARRAY = new String[] {}; 057 058 private static final File CURRENT_DIR = new File( "." ); 059 060 private ScmLogger logger; 061 062 private Commandline cl = new Commandline(); 063 064 private StringBuilder commandLines = new StringBuilder(); 065 066 private StringBuilder errorOutput = new StringBuilder(); 067 068 private StreamConsumer systemErr; 069 070 private String[] hostArgs = EMPTY_STRING_ARRAY; 071 072 private String[] authArgs = EMPTY_STRING_ARRAY; 073 074 private String executable = "accurev"; 075 076 private long executableModTime; 077 078 private String clientVersion; 079 080 public AccuRevCommandLine() 081 { 082 super(); 083 reset(); 084 } 085 086 public AccuRevCommandLine( String host, int port ) 087 { 088 this(); 089 setServer( host, port ); 090 } 091 092 public void setServer( String host, int port ) 093 { 094 095 if ( host != null ) 096 { 097 hostArgs = new String[] { "-H", host + ":" + port }; 098 } 099 else 100 { 101 hostArgs = EMPTY_STRING_ARRAY; 102 } 103 104 } 105 106 public void setExecutable( String accuRevExe ) 107 { 108 109 executable = accuRevExe; 110 reset(); 111 } 112 113 private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements, Pattern matchPattern, 114 List<File> matchedFiles ) 115 throws AccuRevException 116 { 117 118 FileConsumer stdoutConsumer = new FileConsumer( matchedFiles, matchPattern ); 119 120 return executeCommandLine( basedir, args, elements, stdoutConsumer ); 121 } 122 123 private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements, 124 StreamConsumer stdoutConsumer ) 125 throws AccuRevException 126 { 127 128 setWorkingDirectory( basedir ); 129 setCommandLineArgs( args ); 130 131 if ( elements != null ) 132 { 133 for ( File file : elements ) 134 { 135 String path = file.getPath(); 136 // Hack for Windows "/./". TODO find a nicer way to handle this. 137 if ( "\\.".equals( path ) ) 138 { 139 path = "\\.\\"; 140 } 141 cl.createArg().setValue( path ); 142 } 143 } 144 return executeCommandLine( null, stdoutConsumer ) == 0; 145 } 146 147 private void setCommandLineArgs( String[] args ) 148 { 149 150 cl.clearArgs(); 151 152 if ( args.length > 0 ) 153 { 154 // First arg is the accurev command 155 cl.createArg().setValue( args[0] ); 156 157 // Inject -H <host:port> and -A <token> here 158 cl.addArguments( hostArgs ); 159 cl.addArguments( authArgs ); 160 } 161 162 for ( int i = 1; i < args.length; i++ ) 163 { 164 cl.createArg().setValue( args[i] ); 165 } 166 167 } 168 169 private boolean executeCommandLine( String[] args ) 170 throws AccuRevException 171 { 172 173 return executeCommandLine( args, null, null ) == 0; 174 } 175 176 private int executeCommandLine( String[] args, InputStream stdin, StreamConsumer stdout ) 177 throws AccuRevException 178 { 179 180 setCommandLineArgs( args ); 181 182 return executeCommandLine( stdin, stdout ); 183 184 } 185 186 private int executeCommandLine( InputStream stdin, StreamConsumer stdout ) 187 throws AccuRevException 188 { 189 190 commandLines.append( cl.toString() ); 191 commandLines.append( ';' ); 192 193 if ( getLogger().isDebugEnabled() ) 194 { 195 getLogger().debug( cl.toString() ); 196 } 197 try 198 { 199 200 int result = executeCommandLine( cl, stdin, new CommandOutputConsumer( getLogger(), stdout ), systemErr ); 201 if ( result != 0 ) 202 { 203 getLogger().debug( "Non zero result - " + result ); 204 } 205 return result; 206 } 207 catch ( CommandLineException ex ) 208 { 209 throw new AccuRevException( "Error executing command " + cl.toString(), ex ); 210 } 211 212 } 213 214 /** 215 * Extracted so test class can override 216 * 217 * @param stdin 218 * @param stdout 219 * @param stderr 220 * @return 221 * @throws CommandLineException 222 */ 223 protected int executeCommandLine( Commandline cl, InputStream stdin, CommandOutputConsumer stdout, 224 StreamConsumer stderr ) 225 throws CommandLineException 226 { 227 228 int result = CommandLineUtils.executeCommandLine( cl, stdin, stdout, stderr ); 229 stdout.waitComplete(); 230 231 return result; 232 } 233 234 protected Commandline getCommandline() 235 { 236 237 return cl; 238 } 239 240 public void reset() 241 { 242 243 // TODO find out why Commandline allows executable, args etc to be initialised to 244 // null, but not allowing them to be reset to null. This results is weird "clear" 245 // behaviour. It is just safer to start again. 246 247 cl = new Commandline(); 248 commandLines = new StringBuilder(); 249 errorOutput = new StringBuilder(); 250 systemErr = new ErrorConsumer( getLogger(), errorOutput ); 251 cl.getShell().setQuotedArgumentsEnabled( true ); 252 cl.setExecutable( executable ); 253 254 try 255 { 256 cl.addSystemEnvironment(); 257 } 258 catch ( Exception e ) 259 { 260 if ( getLogger().isDebugEnabled() ) 261 { 262 getLogger().debug( "Unable to obtain system environment", e ); 263 } 264 else 265 { 266 getLogger().warn( "Unable to obtain system environment" ); 267 } 268 } 269 } 270 271 /** 272 * {@inheritDoc} 273 */ 274 public boolean mkws( String basisStream, String workspaceName, File basedir ) 275 throws AccuRevException 276 { 277 278 setWorkingDirectory( basedir ); 279 String[] mkws = { "mkws", "-b", basisStream, "-w", workspaceName, "-l", basedir.getAbsolutePath() }; 280 281 return executeCommandLine( mkws ); 282 } 283 284 /** 285 * {@inheritDoc} 286 */ 287 public List<File> update( File baseDir, String transactionId ) 288 throws AccuRevException 289 { 290 291 if ( transactionId == null ) 292 { 293 transactionId = "highest"; 294 } 295 String[] update = { "update", "-t", transactionId }; 296 setWorkingDirectory( baseDir ); 297 298 List<File> updatedFiles = new ArrayList<File>(); 299 return executeCommandLine( update, null, new FileConsumer( updatedFiles, FileConsumer.UPDATE_PATTERN ) ) == 0 ? updatedFiles 300 : null; 301 302 } 303 304 /** 305 * {@inheritDoc} 306 */ 307 public List<File> add( File basedir, List<File> elements, String message ) 308 throws AccuRevException 309 { 310 311 if ( StringUtils.isBlank( message ) ) 312 { 313 message = AccuRev.DEFAULT_ADD_MESSAGE; 314 } 315 316 boolean recursive = false; 317 318 if ( elements == null || elements.isEmpty() ) 319 { 320 elements = Collections.singletonList( CURRENT_DIR ); 321 recursive = true; 322 } 323 else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) ) 324 { 325 recursive = true; 326 } 327 328 List<File> addedFiles = new ArrayList<File>(); 329 return executeCommandLine( basedir, new String[] { "add", "-c", message, recursive ? "-R" : null }, elements, 330 FileConsumer.ADD_PATTERN, addedFiles ) ? addedFiles : null; 331 332 } 333 334 public List<File> defunct( File basedir, List<File> files, String message ) 335 throws AccuRevException 336 { 337 338 if ( StringUtils.isBlank( message ) ) 339 { 340 message = AccuRev.DEFAULT_REMOVE_MESSAGE; 341 } 342 343 if ( files == null || files.isEmpty() ) 344 { 345 files = Collections.singletonList( CURRENT_DIR ); 346 } 347 348 ArrayList<File> defunctFiles = new ArrayList<File>(); 349 return executeCommandLine( basedir, new String[] { "defunct", "-c", message }, files, 350 FileConsumer.DEFUNCT_PATTERN, defunctFiles ) ? defunctFiles : null; 351 } 352 353 public List<File> promote( File basedir, List<File> files, String message ) 354 throws AccuRevException 355 { 356 357 if ( StringUtils.isBlank( message ) ) 358 { 359 message = AccuRev.DEFAULT_PROMOTE_MESSAGE; 360 } 361 List<File> promotedFiles = new ArrayList<File>(); 362 return executeCommandLine( basedir, new String[] { "promote", "-K", "-c", message }, files, 363 FileConsumer.PROMOTE_PATTERN, promotedFiles ) ? promotedFiles : null; 364 365 } 366 367 public String getCommandLines() 368 { 369 370 return commandLines.toString(); 371 } 372 373 public String getErrorOutput() 374 { 375 376 return errorOutput.toString(); 377 } 378 379 public void setLogger( ScmLogger logger ) 380 { 381 this.logger = logger; 382 } 383 384 public ScmLogger getLogger() 385 { 386 387 return logger; 388 } 389 390 public boolean mkdepot( String depotName ) 391 throws AccuRevException 392 { 393 394 String[] mkdepot = { "mkdepot", "-p", depotName }; 395 396 return executeCommandLine( mkdepot ); 397 398 } 399 400 public boolean mkstream( String backingStream, String newStreamName ) 401 throws AccuRevException 402 { 403 String[] mkstream = { "mkstream", "-b", backingStream, "-s", newStreamName }; 404 return executeCommandLine( mkstream ); 405 406 } 407 408 public boolean promoteStream( String subStream, String commitMessage, List<File> promotedFiles ) 409 throws AccuRevException 410 { 411 String[] promote = { "promote", "-s", subStream, "-d" }; 412 return executeCommandLine( promote ); 413 414 } 415 416 /** 417 * {@inheritDoc} 418 */ 419 public List<File> promoteAll( File baseDir, String commitMessage ) 420 throws AccuRevException 421 { 422 423 setWorkingDirectory( baseDir ); 424 String[] promote = { "promote", "-p", "-K", "-c", commitMessage }; 425 426 List<File> promotedFiles = new ArrayList<File>(); 427 return executeCommandLine( promote, null, new FileConsumer( promotedFiles, FileConsumer.PROMOTE_PATTERN ) ) == 0 ? promotedFiles 428 : null; 429 430 } 431 432 public AccuRevInfo info( File basedir ) 433 throws AccuRevException 434 { 435 436 setWorkingDirectory( basedir ); 437 String[] info = { "info" }; 438 AccuRevInfo result = new AccuRevInfo( basedir ); 439 440 executeCommandLine( info, null, new InfoConsumer( result ) ); 441 return result; 442 } 443 444 private void setWorkingDirectory( File basedir ) 445 { 446 447 // TODO raise bug against plexus. Null is OK for working directory 448 // but once set to not-null cannot be set back to null! 449 // this is a problem if the old workingdir has been deleted 450 // probably safer to use a new commandline 451 452 if ( basedir == null ) 453 { 454 cl.setWorkingDirectory( "." ); 455 } 456 cl.setWorkingDirectory( basedir ); 457 } 458 459 public boolean reactivate( String workSpaceName ) 460 throws AccuRevException 461 { 462 463 String[] reactivate = { "reactivate", "wspace", workSpaceName }; 464 465 return executeCommandLine( reactivate, null, new CommandOutputConsumer( getLogger(), null ) ) == 0; 466 467 } 468 469 public boolean rmws( String workSpaceName ) 470 throws AccuRevException 471 { 472 473 String[] rmws = { "rmws", "-s", workSpaceName }; 474 475 return executeCommandLine( rmws ); 476 477 } 478 479 public String stat( File element ) 480 throws AccuRevException 481 { 482 483 String[] stat = { "stat", "-fx", element.getAbsolutePath() }; 484 485 StatConsumer statConsumer = new StatConsumer( getLogger() ); 486 executeCommandLine( stat, null, statConsumer ); 487 return statConsumer.getStatus(); 488 489 } 490 491 public boolean chws( File basedir, String workSpaceName, String newBasisStream ) 492 throws AccuRevException 493 { 494 495 setWorkingDirectory( basedir ); 496 return executeCommandLine( new String[] { "chws", "-s", workSpaceName, "-b", newBasisStream, "-l", "." } ); 497 498 } 499 500 public boolean mksnap( String snapShotName, String basisStream ) 501 throws AccuRevException 502 { 503 504 return executeCommandLine( new String[] { "mksnap", "-s", snapShotName, "-b", basisStream, "-t", "now" } ); 505 } 506 507 public List<File> statTag( String streamName ) 508 throws AccuRevException 509 { 510 511 List<File> taggedFiles = new ArrayList<File>(); 512 String[] stat = new String[] { "stat", "-a", "-ffl", "-s", streamName }; 513 return executeCommandLine( null, stat, null, FileConsumer.STAT_PATTERN, taggedFiles ) ? taggedFiles : null; 514 } 515 516 public List<File> stat( File basedir, Collection<File> elements, AccuRevStat statType ) 517 throws AccuRevException 518 { 519 520 boolean recursive = false; 521 522 if ( elements == null || elements.isEmpty() ) 523 { 524 elements = Collections.singletonList( CURRENT_DIR ); 525 recursive = true; 526 } 527 else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) ) 528 { 529 recursive = true; 530 } 531 532 String[] args = { "stat", "-ffr", statType.getStatArg(), recursive ? "-R" : null }; 533 534 List<File> matchingElements = new ArrayList<File>(); 535 return executeCommandLine( basedir, args, elements, statType.getMatchPattern(), matchingElements ) ? matchingElements 536 : null; 537 } 538 539 public List<File> pop( File basedir, Collection<File> elements ) 540 throws AccuRevException 541 { 542 543 if ( elements == null || elements.isEmpty() ) 544 { 545 elements = Collections.singletonList( CURRENT_DIR ); 546 } 547 548 String[] popws = { "pop", "-R" }; 549 550 List<File> poppedFiles = new ArrayList<File>(); 551 return executeCommandLine( basedir, popws, elements, FileConsumer.POPULATE_PATTERN, poppedFiles ) ? poppedFiles 552 : null; 553 } 554 555 public List<File> popExternal( File basedir, String versionSpec, String tranSpec, Collection<File> elements ) 556 throws AccuRevException 557 { 558 559 if ( elements == null || elements.isEmpty() ) 560 { 561 elements = Collections.singletonList( new File( "/./" ) ); 562 } 563 564 if ( StringUtils.isBlank( tranSpec ) ) 565 { 566 tranSpec = "now"; 567 } 568 569 String[] popArgs; 570 if ( AccuRevVersion.isNow( tranSpec ) ) 571 { 572 popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-R" }; 573 } 574 else 575 // this will BARF for pre 4.9.0, but clients are expected to check AccuRevCapability before calling. 576 { 577 popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-t", tranSpec, "-R" }; 578 } 579 580 List<File> poppedFiles = new ArrayList<File>(); 581 return executeCommandLine( basedir, popArgs, elements, FileConsumer.POPULATE_PATTERN, poppedFiles ) ? poppedFiles 582 : null; 583 } 584 585 public CategorisedElements statBackingStream( File basedir, Collection<File> elements ) 586 throws AccuRevException 587 { 588 589 CategorisedElements catElems = new CategorisedElements(); 590 591 if ( elements.isEmpty() ) 592 { 593 return catElems; 594 } 595 String[] args = { "stat", "-b", "-ffr" }; 596 597 return executeCommandLine( basedir, args, elements, new StatBackingConsumer( catElems.getMemberElements(), 598 catElems.getNonMemberElements() ) ) ? catElems 599 : null; 600 601 } 602 603 public List<Transaction> history( String baseStream, String fromTimeSpec, String toTimeSpec, int count, 604 boolean depotHistory, boolean transactionsOnly ) 605 throws AccuRevException 606 { 607 608 String timeSpec = fromTimeSpec; 609 610 if ( toTimeSpec != null ) 611 { 612 timeSpec = timeSpec + "-" + toTimeSpec; 613 } 614 615 if ( count > 0 ) 616 { 617 timeSpec = timeSpec + "." + count; 618 } 619 620 String[] hist = 621 { "hist", transactionsOnly ? "-ftx" : "-fx", depotHistory ? "-p" : "-s", baseStream, "-t", timeSpec }; 622 623 ArrayList<Transaction> transactions = new ArrayList<Transaction>(); 624 HistoryConsumer stdout = new HistoryConsumer( getLogger(), transactions ); 625 return executeCommandLine( hist, null, stdout ) == 0 ? transactions : null; 626 } 627 628 public List<FileDifference> diff( String baseStream, String fromTimeSpec, String toTimeSpec ) 629 throws AccuRevException 630 { 631 String timeSpec = fromTimeSpec + "-" + toTimeSpec; 632 String[] diff = { "diff", "-fx", "-a", "-i", "-v", baseStream, "-V", baseStream, "-t", timeSpec }; 633 634 List<FileDifference> results = new ArrayList<FileDifference>(); 635 DiffConsumer stdout = new DiffConsumer( getLogger(), results ); 636 return executeCommandLine( diff, null, stdout ) < 2 ? results : null; 637 } 638 639 public boolean login( String user, String password ) 640 throws AccuRevException 641 { 642 643 // TODO Raise bug against plexus commandline - can't set workingdir to null 644 // and will get an error if the working directory is deleted. 645 cl.setWorkingDirectory( "." ); 646 authArgs = EMPTY_STRING_ARRAY; 647 AuthTokenConsumer stdout = new AuthTokenConsumer(); 648 649 boolean result; 650 if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) 651 { 652 if ( StringUtils.isBlank( password ) ) 653 { 654 // Ensure blank is passed in. 655 password = "\"\""; 656 } 657 String[] login = { "login", "-A", user, password }; 658 result = executeCommandLine( login, null, stdout ) == 0; 659 } 660 else 661 { 662 String[] login = { "login", "-A", user }; 663 password = StringUtils.clean( password ) + "\n"; 664 byte[] bytes = password.getBytes(); 665 ByteArrayInputStream stdin = new ByteArrayInputStream( bytes ); 666 result = executeCommandLine( login, stdin, stdout ) == 0; 667 668 } 669 670 authArgs = new String[] { "-A", stdout.getAuthToken() }; 671 return result; 672 } 673 674 public boolean logout() 675 throws AccuRevException 676 { 677 678 String[] logout = { "logout" }; 679 return executeCommandLine( logout ); 680 681 } 682 683 public List<BlameLine> annotate( File basedir, File file ) 684 throws AccuRevException 685 { 686 687 String[] annotate = { "annotate", "-ftud" }; 688 List<BlameLine> lines = new ArrayList<BlameLine>(); 689 AnnotateConsumer stdout = new AnnotateConsumer( lines, getLogger() ); 690 691 return executeCommandLine( basedir, annotate, Collections.singletonList( file ), stdout ) ? lines : null; 692 } 693 694 public Map<String, WorkSpace> showRefTrees() 695 throws AccuRevException 696 { 697 698 String[] show = { "show", "-fx", "refs" }; 699 Map<String, WorkSpace> refTrees = new HashMap<String, WorkSpace>(); 700 WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), refTrees ); 701 return executeCommandLine( show, null, stdout ) == 0 ? refTrees : null; 702 } 703 704 public Map<String, WorkSpace> showWorkSpaces() 705 throws AccuRevException 706 { 707 708 String[] show = { "show", "-a", "-fx", "wspaces" }; 709 Map<String, WorkSpace> workSpaces = new HashMap<String, WorkSpace>(); 710 WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), workSpaces ); 711 return executeCommandLine( show, null, stdout ) == 0 ? workSpaces : null; 712 } 713 714 public Stream showStream( String stream ) 715 throws AccuRevException 716 { 717 String[] show = { "show", "-s", stream, "-fx", "streams" }; 718 List<Stream> streams = new ArrayList<Stream>(); 719 StreamsConsumer stdout = new StreamsConsumer( getLogger(), streams ); 720 721 return executeCommandLine( show, null, stdout ) == 0 && streams.size() == 1 ? streams.get( 0 ) : null; 722 } 723 724 public String getExecutable() 725 { 726 727 return executable; 728 } 729 730 public String getClientVersion() 731 throws AccuRevException 732 { 733 734 long lastModified = new File( getExecutable() ).lastModified(); 735 if ( clientVersion == null || executableModTime != lastModified ) 736 { 737 executableModTime = lastModified; 738 739 ClientVersionConsumer stdout = new ClientVersionConsumer(); 740 executeCommandLine( new String[] {}, null, stdout ); 741 clientVersion = stdout.getClientVersion(); 742 } 743 return clientVersion; 744 745 } 746 747 public boolean syncReplica() 748 throws AccuRevException 749 { 750 return executeCommandLine( new String[] { "replica", "sync" } ); 751 } 752 753 }