1 /******************************************************************************* 2 * Copyright (c) 2000, 2009 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 *******************************************************************************/ 14 package org.eclipse.team.internal.ccvs.core; 15 16 import java.io.*; 17 import java.net.URI; 18 import java.util.*; 19 20 import org.eclipse.core.resources.*; 21 import org.eclipse.core.resources.team.*; 22 import org.eclipse.core.runtime.*; 23 import org.eclipse.core.runtime.Status; 24 import org.eclipse.core.runtime.jobs.ISchedulingRule; 25 import org.eclipse.osgi.util.NLS; 26 import org.eclipse.team.core.RepositoryProvider; 27 import org.eclipse.team.core.TeamException; 28 import org.eclipse.team.core.history.IFileHistoryProvider; 29 import org.eclipse.team.internal.ccvs.core.client.*; 30 import org.eclipse.team.internal.ccvs.core.client.Command.KSubstOption; 31 import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption; 32 import org.eclipse.team.internal.ccvs.core.client.listeners.*; 33 import org.eclipse.team.internal.ccvs.core.filehistory.CVSFileHistoryProvider; 34 import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; 35 import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer; 36 import org.eclipse.team.internal.ccvs.core.syncinfo.*; 37 import org.eclipse.team.internal.ccvs.core.util.*; 38 import org.eclipse.team.internal.core.streams.CRLFtoLFInputStream; 39 import org.eclipse.team.internal.core.streams.LFtoCRLFInputStream; 40 41 /** 42 * CVS implementation of {@link RepositoryProvider} 43 */ 44 public class CVSTeamProvider extends RepositoryProvider { 45 46 private static final ResourceRuleFactory RESOURCE_RULE_FACTORY = new CVSResourceRuleFactory(); 47 48 private static final boolean IS_CRLF_PLATFORM = Arrays.equals( 49 System.getProperty("line.separator").getBytes(), new byte[] { '\r', '\n' }); //$NON-NLS-1$ 50 51 public static final IStatus OK = new Status(IStatus.OK, CVSProviderPlugin.ID, 0, CVSMessages.ok, null); 52 53 private CVSWorkspaceRoot workspaceRoot; 54 private IProject project; 55 56 private static MoveDeleteHook moveDeleteHook= new MoveDeleteHook(); 57 private static CVSCoreFileModificationValidator fileModificationValidator; 58 private static CVSFileHistoryProvider fileHistoryProvider; 59 60 // property used to indicate whether new directories should be discovered for the project 61 private final static QualifiedName FETCH_ABSENT_DIRECTORIES_PROP_KEY = 62 new QualifiedName("org.eclipse.team.cvs.core", "fetch_absent_directories"); //$NON-NLS-1$ //$NON-NLS-2$ 63 // property used to indicate whether the project is configured to use Watch/edit 64 private final static QualifiedName WATCH_EDIT_PROP_KEY = 65 new QualifiedName("org.eclipse.team.cvs.core", "watch_edit"); //$NON-NLS-1$ //$NON-NLS-2$ 66 67 /** 68 * Session property key used to indicate that the project, although not officially shared, 69 * is a target of a CVS operation. 70 */ 71 private static final QualifiedName TEMP_SHARED = new QualifiedName(CVSProviderPlugin.ID, "tempShare"); //$NON-NLS-1$ 72 73 /** 74 * Return whether the project is mapped to CVS or is the target of a CVS operation 75 * that will most likely lead to the project being shared. 76 * @param project the project 77 * @return whether the project is mapped to CVS or is the target of a CVS operation 78 * that will most likely lead to the project being shared 79 */ isSharedWithCVS(IProject project)80 public static boolean isSharedWithCVS(IProject project) { 81 if (project.isAccessible()) { 82 if (RepositoryProvider.isShared(project)) { 83 RepositoryProvider provider = RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId()); 84 if (provider != null) 85 return true; 86 } 87 try { 88 Object sessionProperty = project.getSessionProperty(TEMP_SHARED); 89 return sessionProperty != null && sessionProperty.equals(Boolean.TRUE); 90 } catch (CoreException e) { 91 CVSProviderPlugin.log(e); 92 } 93 } 94 return false; 95 } 96 97 /** 98 * Mark the project as being a target of a CVS operation so the sync info management 99 * will occur. 100 * @param project the project 101 */ markAsTempShare(IProject project)102 public static void markAsTempShare(IProject project) { 103 if (RepositoryProvider.isShared(project)) 104 return; 105 try { 106 project.setSessionProperty(CVSTeamProvider.TEMP_SHARED, Boolean.TRUE); 107 } catch (CoreException e) { 108 CVSProviderPlugin.log(e); 109 } 110 } 111 112 /** 113 * Return the file modification validator used for all CVS repository providers. 114 * @return the file modification validator used for all CVS repository providers 115 */ internalGetFileModificationValidator()116 protected static CVSCoreFileModificationValidator internalGetFileModificationValidator() { 117 if (CVSTeamProvider.fileModificationValidator == null) { 118 CVSTeamProvider.fileModificationValidator = new CVSCoreFileModificationValidator(); 119 } 120 return CVSTeamProvider.fileModificationValidator; 121 } 122 123 /** 124 * No-arg Constructor for IProjectNature conformance 125 */ CVSTeamProvider()126 public CVSTeamProvider() { 127 } 128 129 @Override deconfigure()130 public void deconfigure() { 131 } 132 133 @Override deconfigured()134 public void deconfigured() { 135 // when a nature is removed from the project, notify the synchronizer that 136 // we no longer need the sync info cached. This does not affect the actual CVS 137 // meta directories on disk, and will remain unless a client calls unmanage(). 138 try { 139 EclipseSynchronizer.getInstance().deconfigure(getProject(), null); 140 internalSetWatchEditEnabled(null); 141 internalSetFetchAbsentDirectories(null); 142 } catch(CVSException e) { 143 // Log the exception and let the disconnect continue 144 CVSProviderPlugin.log(e); 145 } 146 ResourceStateChangeListeners.getListener().projectDeconfigured(getProject()); 147 } 148 @Override getProject()149 public IProject getProject() { 150 return project; 151 } 152 153 @Override setProject(IProject project)154 public void setProject(IProject project) { 155 this.project = project; 156 this.workspaceRoot = new CVSWorkspaceRoot(project); 157 // We used to check to see if the project had CVS folders and log 158 // if it didn't However, in some scenarios, the project can be mapped 159 // before the CVS folders have been created (see bug 173610) 160 } 161 162 /** 163 * Return the remote location to which the receiver's project is mapped. 164 */ getRemoteLocation()165 public ICVSRepositoryLocation getRemoteLocation() throws CVSException { 166 try { 167 return workspaceRoot.getRemoteLocation(); 168 } catch (CVSException e) { 169 // If we can't get the remote location, we should disconnect since nothing can be done with the provider 170 try { 171 RepositoryProvider.unmap(project); 172 } catch (TeamException ex) { 173 CVSProviderPlugin.log(ex); 174 } 175 // We need to trigger a decorator refresh 176 throw e; 177 } 178 } 179 getCVSWorkspaceRoot()180 public CVSWorkspaceRoot getCVSWorkspaceRoot() { 181 return workspaceRoot; 182 } 183 184 /* 185 * Generate an exception if the resource is not a child of the project 186 */ checkIsChild(IResource resource)187 private void checkIsChild(IResource resource) throws CVSException { 188 if (!isChildResource(resource)) 189 throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, 190 NLS.bind(CVSMessages.CVSTeamProvider_invalidResource, (new Object[] {resource.getFullPath().toString(), project.getName()})), 191 null)); 192 } 193 194 /* 195 * Get the arguments to be passed to a commit or update 196 */ getValidArguments(IResource[] resources, LocalOption[] options)197 private String[] getValidArguments(IResource[] resources, LocalOption[] options) throws CVSException { 198 List<String> arguments = new ArrayList<>(resources.length); 199 for (IResource resource : resources) { 200 checkIsChild(resource); 201 IPath cvsPath = resource.getFullPath().removeFirstSegments(1); 202 if (cvsPath.segmentCount() == 0) { 203 arguments.add(Session.CURRENT_LOCAL_FOLDER); 204 } else { 205 arguments.add(cvsPath.toString()); 206 } 207 } 208 return arguments.toArray(new String[arguments.size()]); 209 } 210 getCVSArguments(IResource[] resources)211 private ICVSResource[] getCVSArguments(IResource[] resources) { 212 ICVSResource[] cvsResources = new ICVSResource[resources.length]; 213 for (int i = 0; i < cvsResources.length; i++) { 214 cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(resources[i]); 215 } 216 return cvsResources; 217 } 218 219 /* 220 * This method expects to be passed an InfiniteSubProgressMonitor 221 */ setRemoteRoot(ICVSRepositoryLocation location, IProgressMonitor monitor)222 public void setRemoteRoot(ICVSRepositoryLocation location, IProgressMonitor monitor) throws TeamException { 223 224 // Check if there is a differnece between the new and old roots 225 final String root = location.getLocation(false); 226 if (root.equals(workspaceRoot.getRemoteLocation())) 227 return; 228 229 try { 230 workspaceRoot.getLocalRoot().run(progress -> { 231 try { 232 // 256 ticks gives us a maximum of 1024 which seems reasonable for folders is a project 233 progress.beginTask(null, 100); 234 final IProgressMonitor monitor1 = Policy.infiniteSubMonitorFor(progress, 100); 235 monitor1.beginTask(null, 256); 236 237 // Visit all the children folders in order to set the root in the folder sync info 238 workspaceRoot.getLocalRoot().accept(new ICVSResourceVisitor() { 239 public void visitFile(ICVSFile file) throws CVSException {} 240 public void visitFolder(ICVSFolder folder) throws CVSException { 241 monitor1.worked(1); 242 FolderSyncInfo info = folder.getFolderSyncInfo(); 243 if (info != null) { 244 monitor1.subTask(NLS.bind(CVSMessages.CVSTeamProvider_updatingFolder, new String[] { info.getRepository() })); 245 MutableFolderSyncInfo newInfo = info.cloneMutable(); 246 newInfo.setRoot(root); 247 folder.setFolderSyncInfo(newInfo); 248 folder.acceptChildren(this); 249 } 250 } 251 }); 252 } finally { 253 progress.done(); 254 } 255 }, monitor); 256 } finally { 257 monitor.done(); 258 } 259 } 260 261 /* 262 * Helper to indicate if the resource is a child of the receiver's project 263 */ isChildResource(IResource resource)264 private boolean isChildResource(IResource resource) { 265 return resource.getProject().getName().equals(project.getName()); 266 } 267 configureProject()268 public void configureProject() throws CoreException { 269 getProject().setSessionProperty(TEMP_SHARED, null); 270 ResourceStateChangeListeners.getListener().projectConfigured(getProject()); 271 } 272 /** 273 * Sets the keyword substitution mode for the specified resources. 274 * <p> 275 * Applies the following rules in order: 276 * </p> 277 * <ul> 278 * <li>If a file is not managed, skips it.</li> 279 * <li>If a file is not changing modes, skips it.</li> 280 * <li>If a file is being changed from binary to text, corrects line delimiters 281 * then commits it, then admins it.</li> 282 * <li>If a file is added, changes the resource sync information locally.</li> 283 * <li>Otherwise commits the file (with FORCE to create a new revision), then admins it.</li> 284 * </ul> 285 * All files that are admin'd are committed with FORCE to prevent other developers from 286 * casually trying to commit pending changes to the repository without first checking out 287 * a new copy. This is not a perfect solution, as they could just as easily do an UPDATE 288 * and not obtain the new keyword sync info. 289 * 290 * @param changeSet a map from IFile to KSubstOption 291 * @param monitor the progress monitor 292 * @return a status code indicating success or failure of the operation 293 * 294 * @throws TeamException 295 */ setKeywordSubstitution(final Map changeSet, final String comment, IProgressMonitor monitor)296 public IStatus setKeywordSubstitution(final Map /* from IFile to KSubstOption */ changeSet, 297 final String comment, 298 IProgressMonitor monitor) throws TeamException { 299 final IStatus[] result = new IStatus[] { ICommandOutputListener.OK }; 300 workspaceRoot.getLocalRoot().run(monitor1 -> { 301 final Map /* from KSubstOption to List of String */ filesToAdmin = new HashMap(); 302 final Collection<ICVSFile> filesToCommitAsText = new HashSet<>(); // need fast lookup 303 final boolean useCRLF = IS_CRLF_PLATFORM && (CVSProviderPlugin.getPlugin().isUsePlatformLineend()); 304 305 /*** determine the resources to be committed and/or admin'd ***/ 306 for (Iterator it1 = changeSet.entrySet().iterator(); it1.hasNext();) { 307 Map.Entry entry1 = (Map.Entry) it1.next(); 308 IFile file = (IFile) entry1.getKey(); 309 KSubstOption toKSubst1 = (KSubstOption) entry1.getValue(); 310 311 // only set keyword substitution if resource is a managed file 312 checkIsChild(file); 313 ICVSFile mFile = CVSWorkspaceRoot.getCVSFileFor(file); 314 if (! mFile.isManaged()) continue; 315 316 // only set keyword substitution if new differs from actual 317 byte[] syncBytes = mFile.getSyncBytes(); 318 KSubstOption fromKSubst = ResourceSyncInfo.getKeywordMode(syncBytes); 319 if (toKSubst1.equals(fromKSubst)) continue; 320 321 // change resource sync info immediately for an outgoing addition 322 if (ResourceSyncInfo.isAddition(syncBytes)) { 323 mFile.setSyncBytes(ResourceSyncInfo.setKeywordMode(syncBytes, toKSubst1), ICVSFile.UNKNOWN); 324 continue; 325 } 326 327 // nothing do to for deletions 328 if (ResourceSyncInfo.isDeletion(syncBytes)) continue; 329 330 // file exists remotely so we'll have to commit it 331 if (fromKSubst.isBinary() && ! toKSubst1.isBinary()) { 332 // converting from binary to text 333 cleanLineDelimiters(file, useCRLF, new NullProgressMonitor()); // XXX need better progress monitoring 334 // remember to commit the cleaned resource as text before admin 335 filesToCommitAsText.add(mFile); 336 } 337 // remember to admin the resource 338 List list1 = (List) filesToAdmin.get(toKSubst1); 339 if (list1 == null) { 340 list1 = new ArrayList(); 341 filesToAdmin.put(toKSubst1, list1); 342 } 343 list1.add(mFile); 344 } 345 346 /*** commit then admin the resources ***/ 347 // compute the total work to be performed 348 int totalWork = filesToCommitAsText.size() + 1; 349 for (Iterator it2 = filesToAdmin.values().iterator(); it2.hasNext();) { 350 List list2 = (List) it2.next(); 351 totalWork += list2.size(); 352 totalWork += 1; // Add 1 for each connection that needs to be made 353 } 354 if (totalWork != 0) { 355 monitor1.beginTask(CVSMessages.CVSTeamProvider_settingKSubst, totalWork); 356 try { 357 // commit files that changed from binary to text 358 // NOTE: The files are committed as text with conversions even if the 359 // resource sync info still says "binary". 360 if (!filesToCommitAsText.isEmpty()) { 361 Session session1 = new Session(workspaceRoot.getRemoteLocation(), workspaceRoot.getLocalRoot(), true /* output to console */); 362 session1.open(Policy.subMonitorFor(monitor1, 1), true /* open for modification */); 363 try { 364 String keywordChangeComment = comment; 365 if (keywordChangeComment == null || keywordChangeComment.length() == 0) 366 keywordChangeComment = CVSMessages.CVSTeamProvider_changingKeywordComment; 367 result[0] = Command.COMMIT.execute( 368 session1, 369 Command.NO_GLOBAL_OPTIONS, 370 new LocalOption[] { Command.DO_NOT_RECURSE, Commit.FORCE, 371 Command.makeArgumentOption(Command.MESSAGE_OPTION, keywordChangeComment) }, 372 filesToCommitAsText.toArray(new ICVSResource[filesToCommitAsText.size()]), 373 filesToCommitAsText, 374 null, 375 Policy.subMonitorFor(monitor1, filesToCommitAsText.size())); 376 } finally { 377 session1.close(); 378 } 379 380 // if errors were encountered, abort 381 if (! result[0].isOK()) return; 382 } 383 384 // admin files that changed keyword substitution mode 385 // NOTE: As confirmation of the completion of a command, the server replies 386 // with the RCS command output if a change took place. Rather than 387 // assume that the command succeeded, we listen for these lines 388 // and update the local ResourceSyncInfo for the particular files that 389 // were actually changed remotely. 390 for (Iterator it3 = filesToAdmin.entrySet().iterator(); it3.hasNext();) { 391 Map.Entry entry2 = (Map.Entry) it3.next(); 392 final KSubstOption toKSubst2 = (KSubstOption) entry2.getKey(); 393 final List list3 = (List) entry2.getValue(); 394 // do it 395 Session session2 = new Session(workspaceRoot.getRemoteLocation(), workspaceRoot.getLocalRoot(), true /* output to console */); 396 session2.open(Policy.subMonitorFor(monitor1, 1), true /* open for modification */); 397 try { 398 result[0] = Command.ADMIN.execute( 399 session2, 400 Command.NO_GLOBAL_OPTIONS, 401 new LocalOption[] { toKSubst2 }, 402 (ICVSResource[]) list3.toArray(new ICVSResource[list3.size()]), 403 new AdminKSubstListener(toKSubst2), 404 Policy.subMonitorFor(monitor1, list3.size())); 405 } finally { 406 session2.close(); 407 } 408 // if errors were encountered, abort 409 if (! result[0].isOK()) return; 410 } 411 } finally { 412 monitor1.done(); 413 } 414 } 415 }, Policy.monitorFor(monitor)); 416 return result[0]; 417 } 418 419 /** 420 * This method translates the contents of a file from binary into text (ASCII). 421 * Fixes the line delimiters in the local file to reflect the platform's 422 * native encoding. Performs CR/LF -> LF or LF -> CR/LF conversion 423 * depending on the platform but does not affect delimiters that are 424 * already correctly encoded. 425 */ cleanLineDelimiters(IFile file, boolean useCRLF, IProgressMonitor progress)426 public static void cleanLineDelimiters(IFile file, boolean useCRLF, IProgressMonitor progress) 427 throws CVSException { 428 try { 429 // convert delimiters in memory 430 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 431 InputStream is = new BufferedInputStream(file.getContents()); 432 try { 433 // Always convert CR/LF into LFs 434 is = new CRLFtoLFInputStream(is); 435 if (useCRLF) { 436 // For CR/LF platforms, translate LFs to CR/LFs 437 is = new LFtoCRLFInputStream(is); 438 } 439 for (int b; (b = is.read()) != -1;) bos.write(b); 440 bos.close(); 441 } finally { 442 is.close(); 443 } 444 // write file back to disk with corrected delimiters if changes were made 445 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); 446 file.setContents(bis, false /*force*/, false /*keepHistory*/, progress); 447 } catch (CoreException e) { 448 throw CVSException.wrapException(file, CVSMessages.CVSTeamProvider_cleanLineDelimitersException, e); 449 } catch (IOException e) { 450 throw CVSException.wrapException(file, CVSMessages.CVSTeamProvider_cleanLineDelimitersException, e); 451 } 452 } 453 454 @Override getID()455 public String getID() { 456 return CVSProviderPlugin.getTypeId(); 457 } 458 459 @Override getMoveDeleteHook()460 public IMoveDeleteHook getMoveDeleteHook() { 461 return moveDeleteHook; 462 } 463 464 @Override getFileModificationValidator()465 public IFileModificationValidator getFileModificationValidator() { 466 return getFileModificationValidator2(); 467 } 468 469 @Override getFileModificationValidator2()470 public FileModificationValidator getFileModificationValidator2() { 471 return internalGetFileModificationValidator(); 472 } 473 474 /** 475 * Checkout (cvs edit) the provided resources so they can be modified locally and committed. 476 * This will make any read-only resources in the list writable and will notify the server 477 * that the file is being edited. This notification may be done immediately or at some 478 * later point depending on whether contact with the server is possible at the time of 479 * invocation or the value of the notify server parameter. 480 * 481 * The recurse parameter is equivalent to the cvs local options -l (<code>true</code>) and 482 * -R (<code>false</code>). The notifyServer parameter can be used to defer server contact 483 * until the next command. This may be appropriate if no shell or progress monitor is available 484 * to the caller. The notification bit field indicates what temporary watches are to be used while 485 * the file is being edited. The possible values that can be ORed together are ICVSFile.EDIT, 486 * ICVSFile.UNEDIT and ICVSFile.COMMIT. There pre-ORed convenience values ICVSFile.NO_NOTIFICATION 487 * and ICVSFile.NOTIFY_ON_ALL are also available. 488 * 489 * @param resources the resources to be edited 490 * @param recurse indicates whether to recurse (-R) or not (-l) 491 * @param notifyServer indicates whether to notify the server now, if possible, 492 * or defer until the next command. 493 * @param notifyForWrittable 494 * @param notification the temporary watches. 495 * @param progress progress monitor to provide progress indication/cancellation or <code>null</code> 496 * @exception CVSException if this method fails. 497 * @since 2.1 498 * 499 * @see CVSTeamProvider#unedit 500 */ edit(IResource[] resources, boolean recurse, boolean notifyServer, final boolean notifyForWritable, final int notification, IProgressMonitor progress)501 public void edit(IResource[] resources, boolean recurse, boolean notifyServer, final boolean notifyForWritable, final int notification, IProgressMonitor progress) throws CVSException { 502 final int notify; 503 if (notification == ICVSFile.NO_NOTIFICATION) { 504 if (CVSProviderPlugin.getPlugin().isWatchOnEdit()) { 505 notify = ICVSFile.NOTIFY_ON_ALL; 506 } else { 507 notify = ICVSFile.NO_NOTIFICATION; 508 } 509 } else { 510 notify = notification; 511 } 512 notifyEditUnedit(resources, recurse, notifyServer, new ICVSResourceVisitor() { 513 public void visitFile(ICVSFile file) throws CVSException { 514 if (notifyForWritable || file.isReadOnly()) 515 file.edit(notify, notifyForWritable, Policy.monitorFor(null)); 516 } 517 public void visitFolder(ICVSFolder folder) throws CVSException { 518 // nothing needs to be done here as the recurse will handle the traversal 519 } 520 }, null /* no scheduling rule */, progress); 521 } 522 523 /** 524 * Unedit the given resources. Any writable resources will be reverted to their base contents 525 * and made read-only and the server will be notified that the file is no longer being edited. 526 * This notification may be done immediately or at some 527 * later point depending on whether contact with the server is possible at the time of 528 * invocation or the value of the notify server parameter. 529 * 530 * The recurse parameter is equivalent to the cvs local options -l (<code>true</code>) and 531 * -R (<code>false</code>). The notifyServer parameter can be used to defer server contact 532 * until the next command. This may be appropriate if no shell or progress monitor is available 533 * to the caller. 534 * 535 * @param resources the resources to be unedited 536 * @param recurse indicates whether to recurse (-R) or not (-l) 537 * @param notifyServer indicates whether to notify the server now, if possible, 538 * or defer until the next command. 539 * @param progress progress monitor to provide progress indication/cancellation or <code>null</code> 540 * @exception CVSException if this method fails. 541 * @since 2.1 542 * 543 * @see CVSTeamProvider#edit 544 */ unedit(IResource[] resources, boolean recurse, boolean notifyServer, IProgressMonitor progress)545 public void unedit(IResource[] resources, boolean recurse, boolean notifyServer, IProgressMonitor progress) throws CVSException { 546 notifyEditUnedit(resources, recurse, notifyServer, new ICVSResourceVisitor() { 547 public void visitFile(ICVSFile file) throws CVSException { 548 if (!file.isReadOnly()) 549 file.unedit(Policy.monitorFor(null)); 550 } 551 public void visitFolder(ICVSFolder folder) throws CVSException { 552 // nothing needs to be done here as the recurse will handle the traversal 553 } 554 }, getProject() /* project scheduling rule */, progress); 555 } 556 557 /* 558 * This method captures the common behavior between the edit and unedit methods. 559 */ notifyEditUnedit(final IResource[] resources, final boolean recurse, final boolean notifyServer, final ICVSResourceVisitor editUneditVisitor, ISchedulingRule rule, IProgressMonitor monitor)560 private void notifyEditUnedit(final IResource[] resources, final boolean recurse, final boolean notifyServer, final ICVSResourceVisitor editUneditVisitor, ISchedulingRule rule, IProgressMonitor monitor) throws CVSException { 561 final CVSException[] exception = new CVSException[] { null }; 562 IWorkspaceRunnable workspaceRunnable = monitor1 -> { 563 final ICVSResource[] cvsResources = getCVSArguments(resources); 564 565 // mark the files locally as being checked out 566 try { 567 for (int i = 0; i < cvsResources.length; i++) { 568 cvsResources[i].accept(editUneditVisitor, recurse); 569 } 570 } catch (CVSException e2) { 571 exception[0] = e2; 572 return; 573 } 574 575 // send the noop command to the server in order to deliver the notifications 576 if (notifyServer) { 577 monitor1.beginTask(null, 100); 578 Session session = new Session(workspaceRoot.getRemoteLocation(), workspaceRoot.getLocalRoot(), true); 579 try { 580 try { 581 session.open(Policy.subMonitorFor(monitor1, 10), true /* open for modification */); 582 } catch (CVSException e1) { 583 // If the connection cannot be opened, just exit normally. 584 // The notifications will be sent when a connection can be made 585 return; 586 } 587 Command.NOOP.execute( 588 session, 589 Command.NO_GLOBAL_OPTIONS, 590 Command.NO_LOCAL_OPTIONS, 591 cvsResources, 592 null, 593 Policy.subMonitorFor(monitor1, 90)); 594 } catch (CVSException e3) { 595 exception[0] = e3; 596 } finally { 597 session.close(); 598 monitor1.done(); 599 } 600 } 601 }; 602 try { 603 ResourcesPlugin.getWorkspace().run(workspaceRunnable, rule, 0, Policy.monitorFor(monitor)); 604 } catch (CoreException e) { 605 if (exception[0] == null) { 606 throw CVSException.wrapException(e); 607 } else { 608 CVSProviderPlugin.log(CVSException.wrapException(e)); 609 } 610 } 611 if (exception[0] != null) { 612 throw exception[0]; 613 } 614 } 615 616 /** 617 * Gets the etchAbsentDirectories. 618 * @return Returns a boolean 619 */ getFetchAbsentDirectories()620 public boolean getFetchAbsentDirectories() throws CVSException { 621 try { 622 String property = getProject().getPersistentProperty(FETCH_ABSENT_DIRECTORIES_PROP_KEY); 623 if (property == null) return CVSProviderPlugin.getPlugin().getFetchAbsentDirectories(); 624 return Boolean.valueOf(property).booleanValue(); 625 } catch (CoreException e) { 626 throw new CVSException(new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_errorGettingFetchProperty, new String[] { project.getName() }), e, project)); 627 } 628 } 629 630 /** 631 * Sets the fetchAbsentDirectories. 632 * @param etchAbsentDirectories The etchAbsentDirectories to set 633 */ setFetchAbsentDirectories(boolean fetchAbsentDirectories)634 public void setFetchAbsentDirectories(boolean fetchAbsentDirectories) throws CVSException { 635 internalSetFetchAbsentDirectories(fetchAbsentDirectories ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); 636 } 637 internalSetFetchAbsentDirectories(String fetchAbsentDirectories)638 public void internalSetFetchAbsentDirectories(String fetchAbsentDirectories) throws CVSException { 639 try { 640 getProject().setPersistentProperty(FETCH_ABSENT_DIRECTORIES_PROP_KEY, fetchAbsentDirectories); 641 } catch (CoreException e) { 642 IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_errorSettingFetchProperty, new String[] { project.getName() }), e, project); 643 throw new CVSException(status); 644 } 645 } 646 647 @Override canHandleLinkedResources()648 public boolean canHandleLinkedResources() { 649 return true; 650 } 651 652 @Override canHandleLinkedResourceURI()653 public boolean canHandleLinkedResourceURI() { 654 return true; 655 } 656 657 @Override validateCreateLink(IResource resource, int updateFlags, IPath location)658 public IStatus validateCreateLink(IResource resource, int updateFlags, IPath location) { 659 return internalValidateCreateLink(resource); 660 } 661 internalValidateCreateLink(IResource resource)662 private IStatus internalValidateCreateLink(IResource resource) { 663 ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor(resource.getParent().getFolder(new Path(resource.getName()))); 664 try { 665 if (cvsFolder.isCVSFolder()) { 666 // There is a remote folder that overlaps with the link so disallow 667 return new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_overlappingRemoteFolder, new String[] { resource.getFullPath().toString() }),resource); 668 } else { 669 ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor(resource.getParent().getFile(new Path(resource.getName()))); 670 if (cvsFile.isManaged()) { 671 // there is an outgoing file deletion that overlaps the link so disallow 672 return new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_overlappingFileDeletion, new String[] { resource.getFullPath().toString() }),resource); 673 } 674 } 675 } catch (CVSException e) { 676 CVSProviderPlugin.log(e); 677 return e.getStatus(); 678 } 679 return Status.OK_STATUS; 680 } 681 682 @Override validateCreateLink(IResource resource, int updateFlags, URI location)683 public IStatus validateCreateLink(IResource resource, int updateFlags, URI location) { 684 return internalValidateCreateLink(resource); 685 } 686 687 /** 688 * Get the editors of the resources by calling the <code>cvs editors</code> command. 689 * 690 * @author <a href="mailto:gregor.kohlwes@csc.com,kohlwes@gmx.net">Gregor Kohlwes</a> 691 * @param resources 692 * @param progress 693 * @return IEditorsInfo[] 694 * @throws CVSException 695 */ editors( IResource[] resources, IProgressMonitor progress)696 public EditorsInfo[] editors( 697 IResource[] resources, 698 IProgressMonitor progress) 699 throws CVSException { 700 701 // Build the local options 702 LocalOption[] commandOptions = new LocalOption[] { 703 }; 704 progress.worked(10); 705 // Build the arguments list 706 String[] arguments = getValidArguments(resources, commandOptions); 707 708 // Build the listener for the command 709 EditorsListener listener = new EditorsListener(); 710 711 // Check if canceled 712 if (progress.isCanceled()) { 713 return new EditorsInfo[0]; 714 } 715 // Build the session 716 Session session = 717 new Session( 718 workspaceRoot.getRemoteLocation(), 719 workspaceRoot.getLocalRoot()); 720 721 // Check if canceled 722 if (progress.isCanceled()) { 723 return new EditorsInfo[0]; 724 } 725 progress.beginTask(null, 100); 726 try { 727 // Opening the session takes 20% of the time 728 session.open(Policy.subMonitorFor(progress, 20), false /* read-only */); 729 730 if (!progress.isCanceled()) { 731 // Execute the editors command 732 Command.EDITORS.execute( 733 session, 734 Command.NO_GLOBAL_OPTIONS, 735 commandOptions, 736 arguments, 737 listener, 738 Policy.subMonitorFor(progress, 80)); 739 } 740 } finally { 741 session.close(); 742 progress.done(); 743 } 744 // Return the infos about the editors 745 return listener.getEditorsInfos(); 746 } 747 748 /** 749 * Return the commit comment template that was provided by the server. 750 * 751 * @return String 752 * @throws CVSException 753 */ getCommitTemplate()754 public String getCommitTemplate() throws CVSException { 755 ICVSFolder localFolder = getCVSWorkspaceRoot().getLocalRoot(); 756 ICVSFile templateFile = CVSWorkspaceRoot.getCVSFileFor( 757 SyncFileWriter.getTemplateFile( 758 (IContainer)localFolder.getIResource())); 759 if (!templateFile.exists()) return null; 760 InputStream in = new BufferedInputStream(templateFile.getContents()); 761 try { 762 ByteArrayOutputStream out = new ByteArrayOutputStream(); 763 int b; 764 do { 765 b = in.read(); 766 if (b != -1) 767 out.write((byte)b); 768 } while (b != -1); 769 out.close(); 770 return new String(out.toString()); 771 } catch (IOException e) { 772 throw CVSException.wrapException(e); 773 } finally { 774 try { 775 in.close(); 776 } catch (IOException e) { 777 // Since we already have the contents, just log this exception 778 CVSProviderPlugin.log(CVSException.wrapException(e)); 779 } 780 } 781 } 782 783 /** 784 * Return true if the project is configured to use watch/edit. A project will use 785 * watch/edit if it was checked out when the global preference to use watch/edit is 786 * turned on. 787 * @return boolean 788 */ isWatchEditEnabled()789 public boolean isWatchEditEnabled() throws CVSException { 790 IProject project = getProject(); 791 try { 792 String property = (String)project.getSessionProperty(WATCH_EDIT_PROP_KEY); 793 if (property == null) { 794 property = project.getPersistentProperty(WATCH_EDIT_PROP_KEY); 795 if (property == null) { 796 // The persistant property for the project was never set (i.e. old project) 797 // Use the global preference to determine if the project is using watch/edit 798 return CVSProviderPlugin.getPlugin().isWatchEditEnabled(); 799 } else { 800 project.setSessionProperty(WATCH_EDIT_PROP_KEY, property); 801 } 802 } 803 return Boolean.valueOf(property).booleanValue(); 804 } catch (CoreException e) { 805 if (project.isAccessible()) { 806 // We only care if the project still exists 807 IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_errorGettingWatchEdit, new String[] { project.getName() }), e, project); 808 throw new CVSException(status); 809 } 810 } 811 return false; 812 } 813 setWatchEditEnabled(boolean enabled)814 public void setWatchEditEnabled(boolean enabled) throws CVSException { 815 internalSetWatchEditEnabled(enabled ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); 816 } 817 internalSetWatchEditEnabled(String enabled)818 private void internalSetWatchEditEnabled(String enabled) throws CVSException { 819 try { 820 IProject project = getProject(); 821 project.setPersistentProperty(WATCH_EDIT_PROP_KEY, enabled); 822 project.setSessionProperty(WATCH_EDIT_PROP_KEY, enabled); 823 } catch (CoreException e) { 824 IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.CVSTeamProvider_errorSettingWatchEdit, new String[] { project.getName() }), e, project); 825 throw new CVSException(status); 826 } 827 } 828 829 @Override getRuleFactory()830 public IResourceRuleFactory getRuleFactory() { 831 return RESOURCE_RULE_FACTORY; 832 } 833 834 @Override getFileHistoryProvider()835 public IFileHistoryProvider getFileHistoryProvider() { 836 if (CVSTeamProvider.fileHistoryProvider == null) { 837 CVSTeamProvider.fileHistoryProvider = new CVSFileHistoryProvider(); 838 } 839 return CVSTeamProvider.fileHistoryProvider; 840 } 841 } 842