| /******************************************************************************* |
| * Copyright (c) 2006 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ccvs.ui.mappings; |
| |
| import java.util.*; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.diff.*; |
| import org.eclipse.team.core.diff.provider.DiffTree; |
| import org.eclipse.team.core.subscribers.Subscriber; |
| import org.eclipse.team.core.synchronize.SyncInfo; |
| import org.eclipse.team.core.synchronize.SyncInfoSet; |
| import org.eclipse.team.core.variants.IResourceVariant; |
| import org.eclipse.team.internal.ccvs.core.*; |
| import org.eclipse.team.internal.ccvs.core.mapping.CVSCheckedInChangeSet; |
| import org.eclipse.team.internal.ccvs.core.resources.RemoteResource; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; |
| import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin; |
| import org.eclipse.team.internal.ccvs.ui.Policy; |
| import org.eclipse.team.internal.ccvs.ui.operations.RemoteLogOperation.LogEntryCache; |
| import org.eclipse.team.internal.ccvs.ui.subscriber.CVSChangeSetCollector; |
| import org.eclipse.team.internal.ccvs.ui.subscriber.LogEntryCacheUpdateHandler; |
| import org.eclipse.team.internal.ccvs.ui.subscriber.LogEntryCacheUpdateHandler.ILogsFetchedListener; |
| import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter; |
| import org.eclipse.team.internal.core.subscribers.*; |
| import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration; |
| import org.eclipse.team.ui.synchronize.SynchronizePageActionGroup; |
| |
| public class CheckedInChangeSetCollector extends BatchingChangeSetManager implements ILogsFetchedListener { |
| |
| /* |
| * Constant used to store the log entry handler in the configuration so it can |
| * be kept around over layout changes |
| */ |
| private static final String LOG_ENTRY_HANDLER = CVSUIPlugin.ID + ".LogEntryHandler"; //$NON-NLS-1$ |
| |
| /* ***************************************************************************** |
| * Special sync info that has its kind already calculated. |
| */ |
| private class CVSUpdatableSyncInfo extends CVSSyncInfo { |
| public int kind; |
| public CVSUpdatableSyncInfo(int kind, IResource local, IResourceVariant base, IResourceVariant remote, Subscriber s) { |
| super(local, base, remote, s); |
| this.kind = kind; |
| } |
| |
| @Override |
| protected int calculateKind() throws TeamException { |
| return kind; |
| } |
| } |
| |
| IDiffChangeListener diffTreeListener = new IDiffChangeListener() { |
| @Override |
| public void propertyChanged(IDiffTree tree, int property, IPath[] paths) { |
| // Ignore |
| } |
| @Override |
| public void diffsChanged(IDiffChangeEvent event, IProgressMonitor monitor) { |
| if (event.getTree().isEmpty()) { |
| ChangeSet changeSet = getChangeSet(event.getTree()); |
| if (changeSet != null) { |
| remove(changeSet); |
| } |
| } else { |
| ChangeSet changeSet = getChangeSet(event.getTree()); |
| if (changeSet != null) { |
| fireResourcesChangedEvent(changeSet, getAffectedPaths(event)); |
| } |
| } |
| } |
| private IPath[] getAffectedPaths(IDiffChangeEvent event) { |
| Set<IPath> result = new HashSet<>(); |
| IPath[] removed = event.getRemovals(); |
| for (int i = 0; i < removed.length; i++) { |
| IPath path = removed[i]; |
| result.add(path); |
| } |
| IDiff[] diffs = event.getAdditions(); |
| for (int j = 0; j < diffs.length; j++) { |
| IDiff diff = diffs[j]; |
| result.add(diff.getPath()); |
| } |
| diffs = event.getChanges(); |
| for (int j = 0; j < diffs.length; j++) { |
| IDiff diff = diffs[j]; |
| result.add(diff.getPath()); |
| } |
| return result.toArray(new IPath[result.size()]); |
| } |
| }; |
| |
| private final ISynchronizePageConfiguration configuration; |
| private boolean disposed; |
| private LogEntryCache logEntryCache; |
| private final Subscriber subscriber; |
| |
| private HashSet<ChangeSet> updatedSets; |
| |
| public CheckedInChangeSetCollector(ISynchronizePageConfiguration configuration, Subscriber subscriber) { |
| this.configuration = configuration; |
| this.subscriber = subscriber; |
| } |
| |
| /** |
| * Return the configuration for the page that is displaying the model created |
| * using this collector. |
| * @return the configuration for the page that is displaying the model created |
| * using this collector |
| */ |
| public final ISynchronizePageConfiguration getConfiguration() { |
| return configuration; |
| } |
| |
| @Override |
| protected void handleSetAdded(ChangeSet set) { |
| ((DiffChangeSet)set).getDiffTree().addDiffChangeListener(diffTreeListener); |
| super.handleSetAdded(set); |
| if (updatedSets != null) { |
| updatedSets.add(set); |
| ((DiffTree)((DiffChangeSet)set).getDiffTree()).beginInput(); |
| } |
| } |
| |
| @Override |
| protected void handleSetRemoved(ChangeSet set) { |
| ((DiffChangeSet)set).getDiffTree().removeDiffChangeListener(diffTreeListener); |
| super.handleSetRemoved(set); |
| } |
| |
| protected ChangeSet getChangeSet(IDiffTree tree) { |
| ChangeSet[] sets = getSets(); |
| for (int i = 0; i < sets.length; i++) { |
| ChangeSet changeSet = sets[i]; |
| if (((DiffChangeSet)changeSet).getDiffTree() == tree) { |
| return changeSet; |
| } |
| } |
| return null; |
| } |
| |
| public void handleChange(IDiffChangeEvent event) { |
| List<IPath> removals = new ArrayList<>(); |
| List<IDiff> additions = new ArrayList<>(); |
| removals.addAll(Arrays.asList(event.getRemovals())); |
| additions.addAll(Arrays.asList(event.getAdditions())); |
| IDiff[] changed = event.getChanges(); |
| for (int i = 0; i < changed.length; i++) { |
| IDiff diff = changed[i]; |
| additions.add(diff); |
| removals.add(diff.getPath()); |
| } |
| if (!removals.isEmpty()) { |
| remove(removals.toArray(new IPath[removals.size()])); |
| } |
| if (!additions.isEmpty()) { |
| add(additions.toArray(new IDiff[additions.size()])); |
| } |
| } |
| |
| protected void remove(IPath[] paths) { |
| ChangeSet[] sets = getSets(); |
| for (int i = 0; i < sets.length; i++) { |
| DiffChangeSet set = (DiffChangeSet)sets[i]; |
| set.remove(paths); |
| } |
| } |
| |
| public synchronized LogEntryCacheUpdateHandler getLogEntryHandler() { |
| LogEntryCacheUpdateHandler handler = (LogEntryCacheUpdateHandler)getConfiguration().getProperty(LOG_ENTRY_HANDLER); |
| if (handler == null) { |
| handler = initializeLogEntryHandler(getConfiguration()); |
| } |
| handler.setListener(this); |
| return handler; |
| } |
| |
| /* |
| * Initialize the log entry handler and place it in the configuration |
| */ |
| private LogEntryCacheUpdateHandler initializeLogEntryHandler(final ISynchronizePageConfiguration configuration) { |
| final LogEntryCacheUpdateHandler logEntryHandler = new LogEntryCacheUpdateHandler(configuration); |
| configuration.setProperty(LOG_ENTRY_HANDLER, logEntryHandler); |
| // Use an action group to get notified when the configuration is disposed |
| configuration.addActionContribution(new SynchronizePageActionGroup() { |
| @Override |
| public void dispose() { |
| super.dispose(); |
| LogEntryCacheUpdateHandler handler = (LogEntryCacheUpdateHandler)configuration.getProperty(LOG_ENTRY_HANDLER); |
| if (handler != null) { |
| handler.shutdown(); |
| configuration.setProperty(LOG_ENTRY_HANDLER, null); |
| } |
| } |
| }); |
| return logEntryHandler; |
| } |
| |
| protected void add(IDiff[] diffs) { |
| LogEntryCacheUpdateHandler handler = getLogEntryHandler(); |
| if (handler != null) |
| try { |
| handler.fetch(getSyncInfos(diffs)); |
| } catch (CVSException e) { |
| CVSUIPlugin.log(e); |
| } |
| } |
| |
| private SyncInfo[] getSyncInfos(IDiff[] diffs) { |
| SyncInfoSet set = new SyncInfoSet(); |
| for (int i = 0; i < diffs.length; i++) { |
| IDiff diff = diffs[i]; |
| set.add(getConverter().asSyncInfo(diff, getSubscriber().getResourceComparator())); |
| } |
| return set.getSyncInfos(); |
| } |
| |
| public Subscriber getSubscriber() { |
| return subscriber; |
| } |
| |
| @Override |
| public void dispose() { |
| // No longer listen for log entry changes |
| // (The handler is disposed with the page) |
| disposed = true; |
| LogEntryCacheUpdateHandler handler = getLogEntryHandler(); |
| if (handler != null) handler.setListener(null); |
| getConfiguration().setProperty(CVSChangeSetCollector.CVS_CHECKED_IN_COLLECTOR, null); |
| logEntryCache = null; |
| super.dispose(); |
| } |
| |
| /** |
| * Fetch the log histories for the remote changes and use this information |
| * to add each resource to an appropriate commit set. |
| */ |
| private void handleRemoteChanges(final SyncInfo[] infos, final LogEntryCache logEntries, final IProgressMonitor monitor) { |
| try { |
| beginSetUpdate(); |
| addLogEntries(infos, logEntries, monitor); |
| } finally { |
| endSetUpdate(monitor); |
| } |
| } |
| |
| private void beginSetUpdate() { |
| updatedSets = new HashSet<>(); |
| } |
| |
| private void endSetUpdate(IProgressMonitor monitor) { |
| for (Iterator iter = updatedSets.iterator(); iter.hasNext();) { |
| DiffChangeSet set = (DiffChangeSet) iter.next(); |
| try { |
| ((DiffTree)set.getDiffTree()).endInput(monitor); |
| } catch (RuntimeException e) { |
| CVSUIPlugin.log(IStatus.ERROR, "Internal error", e); //$NON-NLS-1$ |
| } |
| } |
| updatedSets = null; |
| } |
| |
| /* |
| * Add the following sync info elements to the viewer. It is assumed that these elements have associated |
| * log entries cached in the log operation. |
| */ |
| private void addLogEntries(SyncInfo[] commentInfos, LogEntryCache logs, IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(null, commentInfos.length * 10); |
| if (logs != null) { |
| for (int i = 0; i < commentInfos.length; i++) { |
| addSyncInfoToCommentNode(commentInfos[i], logs); |
| monitor.worked(10); |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /* |
| * Create a node for the given sync info object. The logs should contain the log for this info. |
| * |
| * @param info the info for which to create a node in the model |
| * @param log the cvs log for this node |
| */ |
| private void addSyncInfoToCommentNode(SyncInfo info, LogEntryCache logs) { |
| LogEntryCacheUpdateHandler handler = getLogEntryHandler(); |
| if (handler != null) { |
| ICVSRemoteResource remoteResource = handler.getRemoteResource(info); |
| if(handler.getSubscriber() instanceof CVSCompareSubscriber && remoteResource != null) { |
| addMultipleRevisions(info, logs, remoteResource); |
| } else { |
| addSingleRevision(info, logs, remoteResource); |
| } |
| } |
| } |
| |
| /* |
| * Add a single log entry to the model. |
| * |
| * @param info |
| * @param logs |
| * @param remoteResource |
| */ |
| private void addSingleRevision(SyncInfo info, LogEntryCache logs, ICVSRemoteResource remoteResource) { |
| ILogEntry logEntry = logs.getLogEntry(remoteResource); |
| if (remoteResource != null && !remoteResource.isFolder()) { |
| // For incoming deletions grab the comment for the latest on the same branch |
| // which is now in the attic. |
| try { |
| String remoteRevision = ((ICVSRemoteFile) remoteResource).getRevision(); |
| if (isDeletedRemotely(info)) { |
| ILogEntry[] logEntries = logs.getLogEntries(remoteResource); |
| for (int i = 0; i < logEntries.length; i++) { |
| ILogEntry entry = logEntries[i]; |
| String revision = entry.getRevision(); |
| if (entry.isDeletion() && ResourceSyncInfo.isLaterRevision(revision, remoteRevision)) { |
| logEntry = entry; |
| } |
| } |
| } |
| } catch (TeamException e) { |
| // continue and skip deletion checks |
| } |
| } |
| addRemoteChange(info, remoteResource, logEntry); |
| } |
| |
| /* |
| * Add multiple log entries to the model. |
| * |
| * @param info |
| * @param logs |
| * @param remoteResource |
| */ |
| private void addMultipleRevisions(SyncInfo info, LogEntryCache logs, ICVSRemoteResource remoteResource) { |
| ILogEntry[] logEntries = logs.getLogEntries(remoteResource); |
| if(logEntries == null || logEntries.length == 0) { |
| // If for some reason we don't have a log entry, try the latest |
| // remote. |
| addRemoteChange(info, null, null); |
| } else { |
| for (int i = 0; i < logEntries.length; i++) { |
| ILogEntry entry = logEntries[i]; |
| addRemoteChange(info, remoteResource, entry); |
| } |
| } |
| } |
| |
| private boolean isDeletedRemotely(SyncInfo info) { |
| int kind = info.getKind(); |
| if(kind == (SyncInfo.INCOMING | SyncInfo.DELETION)) return true; |
| if(SyncInfo.getDirection(kind) == SyncInfo.CONFLICTING && info.getRemote() == null) return true; |
| return false; |
| } |
| |
| /* |
| * Add the remote change to an incoming commit set |
| */ |
| private void addRemoteChange(SyncInfo info, ICVSRemoteResource remoteResource, ILogEntry logEntry) { |
| if (disposed) return; |
| LogEntryCacheUpdateHandler handler = getLogEntryHandler(); |
| if(handler != null && remoteResource != null && logEntry != null && handler.isRemoteChange(info)) { |
| if(requiresCustomSyncInfo(info, remoteResource, logEntry)) { |
| info = new CVSUpdatableSyncInfo(info.getKind(), info.getLocal(), info.getBase(), (RemoteResource)logEntry.getRemoteFile(), getSubscriber()); |
| try { |
| info.init(); |
| } catch (TeamException e) { |
| // this shouldn't happen, we've provided our own calculate kind |
| } |
| } |
| IDiff diff = getConverter().getDeltaFor(info); |
| // Only add the info if the base and remote differ |
| IResourceVariant base = info.getBase(); |
| IResourceVariant remote = info.getRemote(); |
| if ((base == null && remote != null) || (remote == null && base != null) || (remote != null && base != null && !base.equals(remote))) { |
| synchronized(this) { |
| CVSCheckedInChangeSet set = getChangeSetFor(logEntry); |
| if (set == null) { |
| set = createChangeSetFor(logEntry); |
| add(set); |
| } |
| set.add(diff); |
| } |
| } |
| } else { |
| // The info was not retrieved for the remote change for some reason. |
| // Add the node to the root |
| //addToDefaultSet(DEFAULT_INCOMING_SET_NAME, info); |
| } |
| } |
| |
| private SyncInfoToDiffConverter getConverter() { |
| SyncInfoToDiffConverter converter = Adapters.adapt(subscriber, SyncInfoToDiffConverter.class); |
| if (converter == null) |
| converter = SyncInfoToDiffConverter.getDefault(); |
| return converter; |
| } |
| |
| private CVSCheckedInChangeSet createChangeSetFor(ILogEntry logEntry) { |
| return new CVSCheckedInChangeSet(logEntry); |
| } |
| |
| private CVSCheckedInChangeSet getChangeSetFor(ILogEntry logEntry) { |
| ChangeSet[] sets = getSets(); |
| for (int i = 0; i < sets.length; i++) { |
| ChangeSet set = sets[i]; |
| if (set instanceof CVSCheckedInChangeSet && |
| set.getComment().equals(logEntry.getComment()) && |
| ((CVSCheckedInChangeSet)set).getAuthor().equals(logEntry.getAuthor())) { |
| return (CVSCheckedInChangeSet)set; |
| } |
| } |
| return null; |
| } |
| |
| private boolean requiresCustomSyncInfo(SyncInfo info, ICVSRemoteResource remoteResource, ILogEntry logEntry) { |
| // Only interested in non-deletions |
| if (logEntry.isDeletion()) return false; |
| // Only require a custom sync info if the remote of the sync info |
| // differs from the remote in the log entry |
| IResourceVariant remote = info.getRemote(); |
| if (remote == null) return true; |
| return !remote.equals(remoteResource); |
| } |
| |
| /* |
| * @see |
| * org.eclipse.team.ui.synchronize.SyncInfoSetChangeSetCollector#waitUntilDone( |
| * org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public void waitUntilDone(IProgressMonitor monitor) { |
| monitor.worked(1); |
| // wait for the event handler to process changes. |
| LogEntryCacheUpdateHandler handler = getLogEntryHandler(); |
| if (handler != null) { |
| while(handler.getEventHandlerJob().getState() != Job.NONE) { |
| monitor.worked(1); |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException e) { |
| } |
| Policy.checkCanceled(monitor); |
| } |
| } |
| monitor.worked(1); |
| } |
| |
| @Override |
| public void logEntriesFetched(SyncInfoSet set, LogEntryCache logEntryCache, IProgressMonitor monitor) { |
| if (disposed) return; |
| // Hold on to the cache so we can use it while commit sets are visible |
| this.logEntryCache = logEntryCache; |
| try { |
| beginInput(); |
| handleRemoteChanges(set.getSyncInfos(), logEntryCache, monitor); |
| } finally { |
| endInput(monitor); |
| } |
| } |
| |
| public ICVSRemoteFile getImmediatePredecessor(ICVSRemoteFile file) throws TeamException { |
| if (logEntryCache != null) |
| return logEntryCache.getImmediatePredecessor(file); |
| return null; |
| } |
| } |