1 /******************************************************************************* 2 * Copyright (c) 2000, 2007 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 179174 CVS client sets timestamps back when replacing 14 *******************************************************************************/ 15 package org.eclipse.team.internal.ccvs.core.client; 16 17 import java.util.*; 18 19 import org.eclipse.core.resources.IFile; 20 import org.eclipse.core.resources.IResource; 21 import org.eclipse.core.runtime.*; 22 import org.eclipse.osgi.util.NLS; 23 import org.eclipse.team.internal.ccvs.core.*; 24 import org.eclipse.team.internal.ccvs.core.client.listeners.ICommandOutputListener; 25 26 /** 27 * Abstract base class for command requests. 28 * Provides a framework for implementing command execution. 29 */ 30 public abstract class Command extends Request { 31 /*** Command singleton instances ***/ 32 public final static Add ADD = new Add(); 33 public final static Admin ADMIN = new Admin(); 34 public final static Annotate ANNOTATE = new Annotate(); 35 public final static Checkout CHECKOUT = new CheckoutWithOverwrite(); 36 public final static Commit COMMIT = new Commit(); 37 public final static Diff DIFF = new Diff(); 38 public final static RDiff RDIFF = new RDiff(); 39 public final static Editors EDITORS = new Editors(); 40 public final static Import IMPORT = new Import(); 41 public final static Log LOG = new Log(); 42 public final static Remove REMOVE = new Remove(); 43 public final static Status STATUS = new Status(); 44 public final static Tag TAG = new Tag(); 45 // The CUSTOM_TAG command has special handling for added and removed resources. 46 // This behavior supports branching with local changes in the workspace 47 public final static Tag CUSTOM_TAG = new Tag(true); 48 public final static RTag RTAG = new RTag(); 49 public final static Update UPDATE = new Update(); 50 public final static Update REPLACE = new Replace(); 51 public final static SyncUpdate SYNCUPDATE = new SyncUpdate(); 52 public final static Version VERSION = new Version(); 53 public final static NOOPCommand NOOP = new NOOPCommand(); 54 55 // Empty argument array 56 public final static String[] NO_ARGUMENTS = new String[0]; 57 58 /*** Global options ***/ 59 // Empty global option array 60 public static final GlobalOption[] NO_GLOBAL_OPTIONS = new GlobalOption[0]; 61 // Do not change file contents 62 public static final GlobalOption DO_NOT_CHANGE = new GlobalOption("-n"); //$NON-NLS-1$ 63 // Do not record this operation into CVS command history 64 public static final GlobalOption DO_NOT_LOG = new GlobalOption("-l"); //$NON-NLS-1$ 65 // Make new working files read-only 66 public static final GlobalOption MAKE_READ_ONLY = new GlobalOption("-r"); //$NON-NLS-1$ 67 // Trace command execution 68 public static final GlobalOption TRACE_EXECUTION = new GlobalOption("-t"); //$NON-NLS-1$ 69 70 /*** Global options: quietness ***/ 71 // Don't be quiet (normal verbosity) 72 public static final QuietOption VERBOSE = new QuietOption(""); //$NON-NLS-1$ 73 // Be somewhat quiet (suppress informational messages) 74 public static final QuietOption PARTLY_QUIET = new QuietOption("-q"); //$NON-NLS-1$ 75 // Be really quiet (silent but for serious problems) 76 public static final QuietOption SILENT = new QuietOption("-Q"); //$NON-NLS-1$ 77 78 /*** Local options: common to many commands ***/ 79 // Empty local option array 80 public static final LocalOption[] NO_LOCAL_OPTIONS = new LocalOption[0]; 81 // valid for: annotate checkout commit diff export log rdiff remove rtag status tag update 82 public static final LocalOption RECURSE = new LocalOption("-R"); //$NON-NLS-1$ 83 public static final LocalOption DO_NOT_RECURSE = new LocalOption("-l"); //$NON-NLS-1$ 84 // valid for: checkout export update 85 public static final LocalOption PRUNE_EMPTY_DIRECTORIES = new LocalOption("-P"); //$NON-NLS-1$ 86 // valid for: checkout export update 87 public static final LocalOption MESSAGE_OPTION = new LocalOption("-m"); //$NON-NLS-1$ 88 89 /*** Local options: keyword substitution mode ***/ 90 // valid for: add admin checkout export import update 91 private static final Map<String, KSubstOption> ksubstOptionMap = new HashMap<>(); 92 public static final KSubstOption KSUBST_BINARY = new KSubstOption("-kb"); //$NON-NLS-1$ 93 public static final KSubstOption KSUBST_TEXT = new KSubstOption("-ko"); //$NON-NLS-1$ 94 public static final KSubstOption KSUBST_TEXT_EXPAND = new KSubstOption("-kkv"); //$NON-NLS-1$ 95 public static final KSubstOption KSUBST_TEXT_EXPAND_LOCKER = new KSubstOption("-kkvl"); //$NON-NLS-1$ 96 public static final KSubstOption KSUBST_TEXT_VALUES_ONLY = new KSubstOption("-kv"); //$NON-NLS-1$ 97 public static final KSubstOption KSUBST_TEXT_KEYWORDS_ONLY = new KSubstOption("-kk"); //$NON-NLS-1$ 98 99 /*** Default command output listener ***/ 100 protected static final ICommandOutputListener DEFAULT_OUTPUT_LISTENER = new CommandOutputListener(); 101 102 /** 103 * Prevents client code from instantiating us. 104 */ Command()105 protected Command() { } 106 107 /** 108 * Provides the default command output listener which is used to accumulate errors. 109 * 110 * Subclasses can override this method in order to properly interpret information 111 * received from the server. 112 */ getDefaultCommandOutputListener()113 protected ICommandOutputListener getDefaultCommandOutputListener() { 114 return DEFAULT_OUTPUT_LISTENER; 115 } 116 117 /** 118 * Sends the command's arguments to the server. 119 * [template method] 120 * <p> 121 * The default implementation sends all arguments. Subclasses may override 122 * this method to provide alternate behaviour. 123 * </p> 124 * 125 * @param session the CVS session 126 * @param arguments the arguments that were supplied by the caller of execute() 127 */ sendArguments(Session session, String[] arguments)128 protected void sendArguments(Session session, String[] arguments) throws CVSException { 129 for (int i = 0; i < arguments.length; ++i) { 130 session.sendArgument(arguments[i]); 131 } 132 } 133 134 /** 135 * Describes the local resource state to the server prior to command execution. 136 * [template method] 137 * <p> 138 * Commands must override this method to inform the server about the state of 139 * local resources using the Entries, Modified, Unchanged, and Questionable 140 * requests as needed. 141 * </p> 142 * <p> 143 * This method should return the resources that are of interest to the 144 * <code>Command#commandFinished()</code> method. In most cases, it 145 * is the same resources that are provided but in some cases (e.g. Commit) 146 * the resources to be passed to the above method are different. 147 * </p> 148 * 149 * @param session the CVS session 150 * @param globalOptions the global options for the command 151 * @param localOptions the local options for the command 152 * @param resources the resource arguments for the command 153 * @param monitor the progress monitor 154 * @return ICVSResource[] 155 */ sendLocalResourceState(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions, ICVSResource[] resources, IProgressMonitor monitor)156 protected abstract ICVSResource[] sendLocalResourceState(Session session, GlobalOption[] globalOptions, 157 LocalOption[] localOptions, ICVSResource[] resources, IProgressMonitor monitor) 158 throws CVSException; 159 160 /** 161 * Cleans up after command execution. 162 * [template method] 163 * <p> 164 * The default implementation is a no-op. Subclasses may override this 165 * method to follow up command execution on the server with clean up 166 * operations on local resources. 167 * </p> 168 * 169 * @param session the CVS session 170 * @param globalOptions the global options for the command 171 * @param localOptions the local options for the command 172 * @param resources the resource arguments for the command 173 * @param monitor the progress monitor 174 * @param status the status accumulated so far. If the code == CVSStatus.SERVER_ERROR 175 * then the command failed 176 * @return status the status past in plus any additional status accumulated during the finish 177 */ commandFinished(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions, ICVSResource[] resources, IProgressMonitor monitor, IStatus status)178 protected IStatus commandFinished(Session session, GlobalOption[] globalOptions, 179 LocalOption[] localOptions, ICVSResource[] resources, IProgressMonitor monitor, 180 IStatus status) throws CVSException { 181 return status; 182 } 183 184 /** 185 * Sends the local working directory path prior to command execution. 186 * [template method] 187 * <p> 188 * The default implementation sends the paths of local root directory 189 * (assuming it exists). Subclasses may override this method to provide 190 * alternate behaviour. 191 * </p> 192 * 193 * @param session the CVS session 194 */ sendLocalWorkingDirectory(Session session)195 protected void sendLocalWorkingDirectory(Session session) throws CVSException { 196 ICVSFolder localRoot = session.getLocalRoot(); 197 if (localRoot.isCVSFolder()) { 198 session.sendLocalRootDirectory(); 199 } else { 200 session.sendConstructedRootDirectory(); 201 } 202 } 203 204 /** 205 * Computes an array of ICVSResources corresponding to command arguments. 206 * [template method] 207 * <p> 208 * The default implementation assumes that all arguments supplied to the 209 * command represent resources in the local root that are to be manipulated. 210 * Subclasses must override this method if this assumption does not hold. 211 * </p> 212 * @param session the CVS session 213 * @param localOptions the command local options 214 * @param arguments the command arguments 215 * @return the resource arguments for the command 216 */ computeWorkResources(Session session, LocalOption[] localOptions, String[] arguments)217 protected ICVSResource[] computeWorkResources(Session session, 218 LocalOption[] localOptions, String[] arguments) throws CVSException { 219 ICVSFolder localRoot = session.getLocalRoot(); 220 221 if (arguments.length == 0) { 222 // As a convenience, passing no arguments to the CVS command 223 // implies the command will operate on the local root folder. 224 return new ICVSResource[] { localRoot }; 225 } else { 226 // Assume all arguments represent resources that are descendants 227 // of the local root folder. 228 ICVSResource[] resources = new ICVSResource[arguments.length]; 229 for (int i = 0; i < arguments.length; i++) { 230 ICVSResource resource = localRoot.getChild(arguments[i]); 231 // file does not exist, it could have been deleted. It doesn't matter 232 // which type we return since only the name of the resource is used 233 // and sent to the server. 234 if(resource==null) { 235 if(localRoot.getName().length()==0) { 236 // Return a folder because it is the safest choice when 237 // localRoot is a handle to the IWorkspaceRoot! 238 resource = localRoot.getFolder(arguments[i]); 239 } else { 240 resource = localRoot.getFile(arguments[i]); 241 } 242 } 243 resources[i] = resource; 244 } 245 return resources; 246 } 247 } 248 249 /** 250 * Send an array of Resources. 251 * @param localOptions 252 * 253 * @see Command#sendFileStructure(ICVSResource,IProgressMonitor,boolean,boolean,boolean) 254 */ sendFileStructure(Session session, ICVSResource[] resources, LocalOption[] localOptions, boolean emptyFolders, IProgressMonitor monitor)255 protected void sendFileStructure(Session session, ICVSResource[] resources, 256 LocalOption[] localOptions, boolean emptyFolders, IProgressMonitor monitor) throws CVSException { 257 checkResourcesManaged(session, resources); 258 259 new FileStructureVisitor(session, localOptions, emptyFolders, true).visit(session, resources, monitor); 260 } 261 262 /** 263 * Checks that all work resources are managed. 264 * @param session TODO 265 * @param resources the resource arguments for the command 266 * 267 * @throws CVSException if some resources are not managed 268 */ checkResourcesManaged(Session session, ICVSResource[] resources)269 protected void checkResourcesManaged(Session session, ICVSResource[] resources) throws CVSException { 270 for (int i = 0; i < resources.length; ++i) { 271 ICVSFolder folder; 272 if (resources[i].isFolder()) { 273 folder = (ICVSFolder) resources[i]; 274 } 275 else { 276 folder = resources[i].getParent(); 277 } 278 if (!folder.isCVSFolder() && folder.exists()) { 279 IStatus status = new CVSStatus(IStatus.ERROR,CVSStatus.ERROR,NLS.bind(CVSMessages.Command_argumentNotManaged, new String[] { folder.getName() }),session.getLocalRoot()); 280 throw new CVSException(status); 281 } 282 } 283 } 284 285 /** 286 * Executes a CVS command. 287 * <p> 288 * Dispatches the commands, retrieves the results, and determines whether or 289 * not an error occurred. A listener may be supplied to capture message text 290 * that would normally be written to the standard error and standard output 291 * streams of a command line CVS client. 292 * </p> 293 * @param session the open CVS session 294 * @param globalOptions the array of global options, or NO_GLOBAL_OPTIONS 295 * @param localOptions the array of local options, or NO_LOCAL_OPTIONS 296 * @param arguments the array of arguments (usually filenames relative to localRoot), or NO_ARGUMENTS 297 * @param listener the command output listener, or null to discard all messages 298 * @param monitor the progress monitor 299 * @return a status code indicating success or failure of the operation 300 * @throws CVSException if a fatal error occurs (e.g. connection timeout) 301 */ execute(final Session session, final GlobalOption[] globalOptions, final LocalOption[] localOptions, final String[] arguments, final ICommandOutputListener listener, IProgressMonitor pm)302 public final IStatus execute(final Session session, final GlobalOption[] globalOptions, 303 final LocalOption[] localOptions, final String[] arguments, final ICommandOutputListener listener, 304 IProgressMonitor pm) throws CVSException { 305 final IStatus[] status = new IStatus[1]; 306 ICVSRunnable job = monitor -> { 307 // update the global and local options 308 GlobalOption[] gOptions = filterGlobalOptions(session, globalOptions); 309 LocalOption[] lOptions = filterLocalOptions(session, gOptions, localOptions); 310 311 // print the invocation string to the console 312 if (session.isOutputToConsole() || Policy.isDebugProtocol()) { 313 IPath commandRootPath; 314 IResource resource = session.getLocalRoot().getIResource(); 315 if (resource == null) { 316 commandRootPath = Path.EMPTY; 317 } else { 318 commandRootPath = resource.getFullPath(); 319 } 320 String line = constructCommandInvocationString(commandRootPath, gOptions, lOptions, arguments); 321 ConsoleListeners.getInstance().commandInvoked(session, line); 322 if (Policy.isDebugProtocol()) Policy.printProtocolLine("CMD> " + line); //$NON-NLS-1$ 323 } 324 325 // run the command 326 try { 327 session.setCurrentCommand(Command.this); 328 status[0] = doExecute(session, gOptions, lOptions, arguments, listener, monitor); 329 notifyConsoleOnCompletion(session, status[0], null); 330 } catch (CVSException e1) { 331 notifyConsoleOnCompletion(session, null, e1); 332 throw e1; 333 } catch (RuntimeException e2) { 334 notifyConsoleOnCompletion(session, null, e2); 335 throw e2; 336 } 337 }; 338 if (isWorkspaceModification()) { 339 session.getLocalRoot().run(job, pm); 340 } else { 341 job.run(pm); 342 } 343 return status[0]; 344 } 345 346 /** 347 * Return whether this command modifies the workspace. 348 * If <code>true</code> is returned, a scheduling rule on 349 * the session local root is obtained. Otherwise, no 350 * scheduling rule is obtained. By default, <code>true</code> 351 * is returned 352 * @return whether this command modifies the workspace 353 */ isWorkspaceModification()354 protected boolean isWorkspaceModification() { 355 return true; 356 } 357 notifyConsoleOnCompletion(Session session, IStatus status, Exception exception)358 private void notifyConsoleOnCompletion(Session session, IStatus status, Exception exception) { 359 ConsoleListeners.getInstance().commandCompleted(session, status, exception); 360 if (Policy.isDebugProtocol()) { 361 if (status != null) Policy.printProtocolLine("RESULT> " + status.toString()); //$NON-NLS-1$ 362 else Policy.printProtocolLine("RESULT> " + exception.toString()); //$NON-NLS-1$ 363 } 364 } 365 doExecute(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions, String[] arguments, ICommandOutputListener listener, IProgressMonitor monitor)366 protected IStatus doExecute(Session session, GlobalOption[] globalOptions, 367 LocalOption[] localOptions, String[] arguments, ICommandOutputListener listener, 368 IProgressMonitor monitor) throws CVSException { 369 ICVSResource[] resources = null; 370 /*** setup progress monitor ***/ 371 monitor = Policy.monitorFor(monitor); 372 monitor.beginTask(null, 100); 373 Policy.checkCanceled(monitor); 374 try { 375 /*** prepare for command ***/ 376 // clear stale command state from previous runs 377 session.setNoLocalChanges(DO_NOT_CHANGE.isElementOf(globalOptions)); 378 session.setModTime(null); 379 380 /*** initiate command ***/ 381 // send global options 382 for (GlobalOption globalOption : globalOptions) { 383 globalOption.send(session); 384 } 385 Policy.checkCanceled(monitor); 386 // send local options 387 for (LocalOption localOption : localOptions) { 388 localOption.send(session); 389 } 390 Policy.checkCanceled(monitor); 391 // compute the work resources 392 resources = computeWorkResources(session, localOptions, arguments); 393 Policy.checkCanceled(monitor); 394 // send local working directory state contributes 48% of work 395 resources = sendLocalResourceState(session, globalOptions, localOptions, 396 resources, Policy.infiniteSubMonitorFor(monitor, 48)); 397 Policy.checkCanceled(monitor); 398 // escape file names, see bug 149683 399 for(int i = 0; i < arguments.length; i++){ 400 if(arguments[i].startsWith("-")){ //$NON-NLS-1$ 401 arguments[i] = "./" + arguments[i]; //$NON-NLS-1$ 402 } 403 } 404 // send arguments 405 sendArguments(session, arguments); 406 // send local working directory path 407 sendLocalWorkingDirectory(session); 408 409 // if no listener was provided, use the command's default in order to get error reporting 410 if (listener == null) listener = getDefaultCommandOutputListener(); 411 412 /*** execute command and process responses ***/ 413 // Processing responses contributes 50% of work. 414 IStatus status = executeRequest(session, listener, Policy.subMonitorFor(monitor, 50)); 415 416 // Finished adds last 2% of work. 417 status = commandFinished(session, globalOptions, localOptions, resources, Policy.subMonitorFor(monitor, 2), 418 status); 419 return status; 420 } finally { 421 monitor.done(); 422 } 423 } 424 425 /** 426 * Constucts the CVS command invocation string corresponding to the arguments. 427 * 428 * @param globalOptions the global options 429 * @param localOption the local options 430 * @param arguments the arguments 431 * @return the command invocation string 432 */ constructCommandInvocationString(IPath commandRootPath, GlobalOption[] globalOptions, LocalOption[] localOptions, String[] arguments)433 private String constructCommandInvocationString(IPath commandRootPath, GlobalOption[] globalOptions, 434 LocalOption[] localOptions, String[] arguments) { 435 StringBuilder commandLine = new StringBuilder("cvs"); //$NON-NLS-1$ 436 for (int i = 0; i < globalOptions.length; ++i) { 437 String option = globalOptions[i].toString(); 438 if (option.length() == 0) continue; 439 commandLine.append(' '); 440 commandLine.append(option); 441 } 442 commandLine.append(' '); 443 commandLine.append(getRequestId()); 444 for (int i = 0; i < localOptions.length; ++i) { 445 String option = localOptions[i].toString(); 446 if (option.length() == 0) continue; 447 commandLine.append(' '); 448 commandLine.append(option); 449 } 450 for (int i = 0; i < arguments.length; ++i) { 451 if (arguments[i].length() == 0) continue; 452 commandLine.append(" \""); //$NON-NLS-1$ 453 IPath completePath = commandRootPath; 454 if (!arguments[i].equals(Session.CURRENT_LOCAL_FOLDER)) { 455 completePath = completePath.append(arguments[i]); 456 } 457 commandLine.append(completePath.toString()); 458 commandLine.append('"'); 459 } 460 return commandLine.toString(); 461 } 462 463 /** 464 * Superclass for all CVS command options 465 */ 466 protected static abstract class Option { 467 protected String option, argument; Option(String option, String argument)468 protected Option(String option, String argument) { 469 this.option = option; 470 this.argument = argument; 471 } 472 /** 473 * Determines if this option is an element of an array of options 474 * @param array the array of options 475 * @return true iff the array contains this option 476 */ isElementOf(Option[] array)477 public boolean isElementOf(Option[] array) { 478 return findOption(array, option) != null; 479 } 480 /** 481 * Returns the option part of the option 482 */ getOption()483 String getOption() { 484 return option; 485 } 486 /** 487 * Compares two options for equality. 488 * @param other the other option 489 */ equals(Object other)490 public boolean equals(Object other) { 491 if (this == other) return true; 492 if (other instanceof Option) { 493 Option otherOption = (Option) other; 494 return option.equals(otherOption.option); 495 } 496 return false; 497 } 498 /** 499 * Sends the option to a CVS server 500 * @param session the CVS session 501 */ send(Session session)502 public abstract void send(Session session) throws CVSException; 503 /* 504 * To make debugging a tad easier. 505 */ toString()506 public String toString() { 507 if (argument != null && argument.length() != 0) { 508 return option + " \"" + argument + '"'; //$NON-NLS-1$ 509 } else { 510 return option; 511 } 512 } 513 } 514 /** 515 * Option subtype for global options that are common to all commands. 516 */ 517 public static class GlobalOption extends Option { GlobalOption(String option)518 protected GlobalOption(String option) { 519 super(option, null); 520 } send(Session session)521 public void send(Session session) throws CVSException { 522 session.sendGlobalOption(option); 523 } 524 /** 525 * Add the given global option to the end of the provided list 526 * 527 * @param newOption 528 * @param options 529 * @return GlobalOption[] 530 */ addToEnd(GlobalOption[] options)531 protected GlobalOption[] addToEnd(GlobalOption[] options) { 532 GlobalOption[] globalOptions = new GlobalOption[options.length + 1]; 533 System.arraycopy(options, 0, globalOptions, 0, options.length); 534 globalOptions[globalOptions.length - 1] = this; 535 return globalOptions; 536 } 537 } 538 /** 539 * Option subtype for global quietness options. 540 */ 541 public static final class QuietOption extends GlobalOption { QuietOption(String option)542 private QuietOption(String option) { 543 super(option); 544 } send(Session session)545 public void send(Session session) throws CVSException { 546 if (option.length() != 0) super.send(session); 547 } 548 } 549 /** 550 * Option subtype for local options that vary from command to command. 551 */ 552 public static class LocalOption extends Option { LocalOption(String option)553 protected LocalOption(String option) { 554 super(option, null); 555 } LocalOption(String option, String argument)556 protected LocalOption(String option, String argument) { 557 super(option, argument); 558 } send(Session session)559 public void send(Session session) throws CVSException { 560 session.sendArgument(option); 561 if (argument != null) session.sendArgument(argument); 562 } addTo(LocalOption[] options)563 public LocalOption[] addTo(LocalOption[] options) { 564 if (this.isElementOf(options)) { 565 return options; 566 } 567 LocalOption[] newOptions = new LocalOption[options.length + 1]; 568 System.arraycopy(options, 0, newOptions, 0, options.length); 569 newOptions[options.length] = this; 570 return newOptions; 571 } removeFrom(LocalOption[] options)572 public LocalOption[] removeFrom(LocalOption[] options) { 573 if (!this.isElementOf(options)) { 574 return options; 575 } 576 List<LocalOption> result = new ArrayList<>(); 577 for (LocalOption option : options) { 578 if (!option.equals(this)) { 579 result.add(option); 580 } 581 } 582 return result.toArray(new LocalOption[result.size()]); 583 } 584 } 585 /** 586 * Options subtype for keyword substitution options. 587 */ 588 public static class KSubstOption extends LocalOption { 589 private boolean isUnknownMode; KSubstOption(String option)590 private KSubstOption(String option) { 591 this(option, false); 592 } KSubstOption(String option, boolean isUnknownMode)593 private KSubstOption(String option, boolean isUnknownMode) { 594 super(option); 595 this.isUnknownMode = isUnknownMode; 596 ksubstOptionMap.put(option, this); 597 } 598 /** 599 * Gets the KSubstOption instance for the specified mode. 600 * 601 * @param mode the mode, e.g. -kb 602 * @return an instance for that mode 603 */ fromMode(String mode)604 public static KSubstOption fromMode(String mode) { 605 if (mode.length() == 0) mode = "-kkv"; // use default //$NON-NLS-1$ 606 KSubstOption option = ksubstOptionMap.get(mode); 607 if (option == null) option = new KSubstOption(mode, true); 608 return option; 609 } 610 /** 611 * Gets the KSubstOption instance for the specified file. 612 * 613 * @param file the file to get the option for 614 * @return an instance for that mode 615 */ fromFile(IFile file)616 public static KSubstOption fromFile(IFile file) { 617 if (CVSProviderPlugin.isText(file)) 618 return getDefaultTextMode(); 619 return KSUBST_BINARY; 620 } 621 /** 622 * Returns an array of all valid modes. 623 */ getAllKSubstOptions()624 public static KSubstOption[] getAllKSubstOptions() { 625 return ksubstOptionMap.values().toArray(new KSubstOption[ksubstOptionMap.size()]); 626 } 627 /** 628 * Returns the entry line mode string for this instance. Note that it might return blank strings 629 * for certain options. For UI, use {@link #toMode()} which will always return the a string 630 * containing the keyword substitution. 631 */ toEntryLineMode()632 public String toEntryLineMode() { 633 if (KSUBST_TEXT_EXPAND.equals(this)) return ""; //$NON-NLS-1$ 634 return getOption(); 635 } 636 637 /** 638 * Returns the entry line mode string for this instance. 639 */ toMode()640 public String toMode(){ 641 return getOption(); 642 } 643 644 /** 645 * Returns true if the substitution mode requires no data translation 646 * during file transfer. 647 */ isBinary()648 public boolean isBinary() { 649 return KSUBST_BINARY.equals(this); 650 } 651 /** 652 * Returns a short localized text string describing this mode. 653 */ getShortDisplayText()654 public String getShortDisplayText() { 655 if (isUnknownMode) 656 return NLS.bind(CVSMessages.KSubstOption_unknown_short, new String[] { option }); 657 if (option.equals("-kb")) //$NON-NLS-1$ 658 return CVSMessages.KSubstOption__kb_short; 659 if (option.equals("-kkv")) //$NON-NLS-1$ 660 return CVSMessages.KSubstOption__kkv_short; 661 if (option.equals("-ko")) //$NON-NLS-1$ 662 return CVSMessages.KSubstOption__ko_short; 663 if (option.equals("-kk")) //$NON-NLS-1$ 664 return CVSMessages.KSubstOption__kk_short; 665 if (option.equals("-kv")) //$NON-NLS-1$ 666 return CVSMessages.KSubstOption__kv_short; 667 if (option.equals("-kkvl")) //$NON-NLS-1$ 668 return CVSMessages.KSubstOption__kkvl_short; 669 return NLS.bind(CVSMessages.KSubstOption_unknown_short, new String[] { option }); 670 } 671 /** 672 * Returns a long localized text string describing this mode. 673 */ getLongDisplayText()674 public String getLongDisplayText() { 675 if (isUnknownMode) 676 return NLS.bind(CVSMessages.KSubstOption_unknown_long, new String[] { option }); 677 if (option.equals("-kb")) //$NON-NLS-1$ 678 return CVSMessages.KSubstOption__kb_long; 679 if (option.equals("-kkv")) //$NON-NLS-1$ 680 return CVSMessages.KSubstOption__kkv_long; 681 if (option.equals("-ko")) //$NON-NLS-1$ 682 return CVSMessages.KSubstOption__ko_long; 683 if (option.equals("-kk")) //$NON-NLS-1$ 684 return CVSMessages.KSubstOption__kk_long; 685 if (option.equals("-kv")) //$NON-NLS-1$ 686 return CVSMessages.KSubstOption__kv_long; 687 if (option.equals("-kkvl")) //$NON-NLS-1$ 688 return CVSMessages.KSubstOption__kkvl_long; 689 return NLS.bind(CVSMessages.KSubstOption_unknown_long, new String[] { option }); 690 } 691 /** 692 * Return the text mode that will be used by default 693 */ getDefaultTextMode()694 public static KSubstOption getDefaultTextMode() { 695 return CVSProviderPlugin.getPlugin().getDefaultTextKSubstOption(); 696 } 697 } 698 699 /** 700 * Makes a -m log message option. 701 * Valid for: add commit import 702 */ makeArgumentOption(LocalOption option, String argument)703 public static LocalOption makeArgumentOption(LocalOption option, String argument) { 704 if(argument == null) { 705 argument = ""; //$NON-NLS-1$ 706 } 707 return new LocalOption(option.getOption(), argument); 708 } 709 710 /** 711 * Makes a -r or -D option for a tag. 712 * Valid for: checkout export history rdiff update 713 */ makeTagOption(CVSTag tag)714 public static LocalOption makeTagOption(CVSTag tag) { 715 int type = tag.getType(); 716 switch (type) { 717 case CVSTag.BRANCH: 718 case CVSTag.VERSION: 719 return new LocalOption("-r", tag.getName()); //$NON-NLS-1$ 720 case CVSTag.DATE: 721 return new LocalOption("-D", tag.getName()); //$NON-NLS-1$ 722 default: 723 // tag must not be HEAD 724 throw new IllegalArgumentException(CVSMessages.Command_invalidTag); 725 } 726 } 727 728 /** 729 * Find a specific option in an array of options 730 * @param array the array of options 731 * @param option the option string to search for 732 * @return the first element matching the option string, or null if none 733 */ findOption(Option[] array, String option)734 public static Option findOption(Option[] array, String option) { 735 for (int i = 0; i < array.length; ++i) { 736 if (array[i].getOption().equals(option)) return array[i]; 737 } 738 return null; 739 } 740 741 /** 742 * Collect all arguments of a specific option from an array of options 743 * @param array the array of options 744 * @param option the option string to search for 745 * @return an array of all arguments of belonging to matching options 746 */ collectOptionArguments(Option[] array, String option)747 protected static String[] collectOptionArguments(Option[] array, String option) { 748 List<String> list = new ArrayList<>(); 749 for (int i = 0; i < array.length; ++i) { 750 if (array[i].getOption().equals(option)) { 751 list.add(array[i].argument); 752 } 753 } 754 return list.toArray(new String[list.size()]); 755 } 756 757 /** 758 * Allows commands to filter the set of global options to be sent. 759 * This method invokes the method of the same name on the session 760 * itself in order to get any session wide or globally set options. 761 * Subclasses that override this method should call the superclass. 762 * 763 * @param session the session 764 * @param globalOptions the global options, read-only 765 * @return the filtered global options 766 */ filterGlobalOptions(Session session, GlobalOption[] globalOptions)767 protected GlobalOption[] filterGlobalOptions(Session session, GlobalOption[] globalOptions) { 768 return session.filterGlobalOptions(globalOptions); 769 } 770 771 /** 772 * Allows commands to filter the set of local options to be sent. 773 * Subclasses that override this method should call the superclass. 774 * 775 * @param session the session 776 * @param globalOptions the global options, read-only 777 * @param localOptions the local options, read-only 778 * @return the filtered local options 779 */ filterLocalOptions(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions)780 protected LocalOption[] filterLocalOptions(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions) { 781 return localOptions; 782 } 783 784 /** 785 * Execute a CVS command on an array of ICVSResource. This method simply converts 786 * the ICVSResource to String paths relative to the local root of the session and 787 * invokes <code>execute(Session, GlobalOption[], LocalOption[], String[], ICommandOutputListener, IProgressMonitor)</code>. 788 * 789 * @param session the open CVS session 790 * @param globalOptions the array of global options, or NO_GLOBAL_OPTIONS 791 * @param localOptions the array of local options, or NO_LOCAL_OPTIONS 792 * @param arguments the array of ICVSResource to be operated on 793 * @param listener the command output listener, or null to discard all messages 794 * @param monitor the progress monitor 795 * @return a status code indicating success or failure of the operation 796 * @throws CVSException if a fatal error occurs (e.g. connection timeout) 797 * 798 * @see Command#execute(Session, GlobalOption[], LocalOption[], String[], ICommandOutputListener, IProgressMonitor) 799 */ execute(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions, ICVSResource[] arguments, ICommandOutputListener listener, IProgressMonitor pm)800 public final IStatus execute(Session session, GlobalOption[] globalOptions, LocalOption[] localOptions, ICVSResource[] arguments, 801 ICommandOutputListener listener, IProgressMonitor pm) throws CVSException { 802 803 String[] stringArguments = convertArgumentsForOpenSession(arguments, session); 804 return execute(session, globalOptions, localOptions, stringArguments, listener, pm); 805 } 806 convertArgumentsForOpenSession(ICVSResource[] arguments, Session openSession)807 protected String[] convertArgumentsForOpenSession(ICVSResource[] arguments, Session openSession) throws CVSException { 808 // Convert arguments 809 List<String> stringArguments = new ArrayList<>(arguments.length); 810 for (ICVSResource argument : arguments) { 811 stringArguments.add(argument.getRelativePath(openSession.getLocalRoot())); 812 } 813 return stringArguments.toArray(new String[stringArguments.size()]); 814 } 815 816 /** 817 * Method mergeStatus. 818 * @param status 819 * @param cVSStatus 820 * @return IStatus 821 */ mergeStatus(IStatus accumulatedStatus, IStatus newStatus)822 protected IStatus mergeStatus(IStatus accumulatedStatus, IStatus newStatus) { 823 if (accumulatedStatus.isMultiStatus()) { 824 ((MultiStatus)accumulatedStatus).merge(newStatus); 825 return accumulatedStatus; 826 } 827 if (accumulatedStatus.isOK()) return newStatus; 828 if (newStatus.isOK()) return accumulatedStatus; 829 MultiStatus result = new MultiStatus(CVSProviderPlugin.ID, IStatus.INFO, 830 new IStatus[] {accumulatedStatus, newStatus}, 831 NLS.bind(CVSMessages.Command_warnings, new String[] { getDisplayText() }), null); 832 return result; 833 } 834 } 835