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