Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean Michel-Lemieux2003-06-11 17:02:30 -0400
committerJean Michel-Lemieux2003-06-11 17:02:30 -0400
commitc3aa670a017c46c32937ef9c420792cb2f6e21aa (patch)
tree0c90e5c63b615d9b8fb350599c22377da51d70c2
parenta1a92608f5b83fb5e9fd40e401513bba6eeb18d4 (diff)
downloadeclipse.platform.team-c3aa670a017c46c32937ef9c420792cb2f6e21aa.tar.gz
eclipse.platform.team-c3aa670a017c46c32937ef9c420792cb2f6e21aa.tar.xz
eclipse.platform.team-c3aa670a017c46c32937ef9c420792cb2f6e21aa.zip
New Live Sync view merged in from branch.
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ComparisonCriteria.java87
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ContentComparisonCriteria.java148
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ITeamResourceChangeListener.java44
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SyncInfo.java346
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/TeamSubscriberFactory.java64
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContext.java138
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLContentHandler.java76
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLWriter.java215
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSRevisionNumberCompareCriteria.java92
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncTreeSubscriber.java269
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java220
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/UpdateMergableOnly.java113
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java20
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/BaseSynchronizer.java48
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/OptimizedRemoteSynchronizer.java77
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteResourceFactory.java21
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteSynchronizer.java316
-rw-r--r--bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ResourceSynchronizer.java64
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSOperation.java305
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSWorkspaceModifyOperation.java44
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java124
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/UpdateOnlyMergableOperation.java93
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/CVSSubscriberAction.java186
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeDialog.java47
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeUpdateAction.java327
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberCommitAction.java305
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberConfirmMergedAction.java65
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberUpdateAction.java279
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SyncResourceSetDetailsDialog.java173
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/UpdateDialog.java180
-rw-r--r--bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/WorkspaceUpdateAction.java74
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/ContributedSubscriberAction.java306
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/FilterSyncViewerAction.java63
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenInCompareAction.java81
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenWithActionGroup.java84
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerAction.java37
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActionGroup.java81
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActions.java481
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerChangeFilters.java224
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerComparisonCriteria.java124
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberActions.java112
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberListActions.java138
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerToolbarDropDownAction.java86
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/LocalResourceTypedElement.java154
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/RemoteResourceTypedElement.java105
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInput.java104
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInputFinder.java71
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoDiffNode.java59
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ChangeFiltersContentProvider.java57
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ISyncSetChangedListener.java24
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SubscriberInput.java97
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncResource.java149
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSet.java390
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetChangedEvent.java157
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetContentProvider.java175
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInput.java107
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSubscriberWorkingSet.java127
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSyncSet.java100
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTableContentProvider.java64
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTreeContentProvider.java108
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerLabelProvider.java102
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerSorter.java40
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerTableSorter.java148
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AndSyncInfoFilter.java35
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AutomergableFilter.java26
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/CompoundSyncInfoFilter.java18
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/OrSyncInfoFilter.java34
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/PseudoConflictFilter.java27
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SubscriberAction.java103
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoChangeTypeFilter.java44
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoDirectionFilter.java42
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoFilter.java39
-rw-r--r--bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncResourceSet.java231
-rw-r--r--tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/AllTestsTeamSubscriber.java33
-rw-r--r--tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSMergeSubscriberTest.java270
-rw-r--r--tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSSyncSubscriberTest.java224
-rw-r--r--tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java1034
77 files changed, 10869 insertions, 6 deletions
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ComparisonCriteria.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ComparisonCriteria.java
new file mode 100644
index 000000000..aacc2a679
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ComparisonCriteria.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.core.subscribers;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * A ComparisonCriteria used by a <code>TeamSubscriber</code> to calculate the sync
+ * state of the workspace resources. Subscribers are free to use the criteria
+ * best suited for their environment. For example, an FTP subscriber could choose to use file
+ * size or file size as compasison criterias.
+ * <p>
+ * Aggregate criterias can be created for cases where a criteria is based on the result
+ * of another criteria.</p>
+ *
+ * @see org.eclipse.team.core.subscribers.SyncInfo
+ * @see org.eclipse.team.core.subscribers.TeamSubscriber
+ */
+abstract public class ComparisonCriteria {
+
+ private ComparisonCriteria[] preConditions;
+
+ /**
+ * Default no-args contructor to be called if the comparison criteria does not
+ * depend on other criterias.
+ */
+ public ComparisonCriteria() {
+ }
+
+ /**
+ * Constructor used to create a criteria whose comparison is based on the compare
+ * result of other criterias.
+ * @param preConditions array of preconditions
+ */
+ public ComparisonCriteria(ComparisonCriteria[] preConditions) {
+ this.preConditions = preConditions;
+ }
+
+ /**
+ * Return the comparison criteria, in a format that is suitable for display to an end
+ * user.
+ */
+ abstract public String getName();
+
+ /**
+ * Return the unique id that identified this comparison criteria.
+ */
+ abstract public String getId();
+
+ /**
+ * Returns <code>true</code> if e1 and e2 are equal based on this criteria and <code>false</code>
+ * otherwise. Since comparison could be long running the caller should provide a progress monitor.
+ *
+ * @param e1 object to be compared
+ * @param e2 object to be compared
+ * @param monitor
+ * @return
+ * @throws TeamException
+ */
+ abstract public boolean compare(Object e1, Object e2, IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * @return
+ */
+ protected ComparisonCriteria[] getPreConditions() {
+ return preConditions;
+ }
+
+ protected boolean checkPreConditions(Object e1, Object e2, IProgressMonitor monitor) throws TeamException {
+ for (int i = 0; i < preConditions.length; i++) {
+ ComparisonCriteria cc = preConditions[i];
+ if(cc.compare(e1, e2, monitor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ContentComparisonCriteria.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ContentComparisonCriteria.java
new file mode 100644
index 000000000..6f081bff2
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ContentComparisonCriteria.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.core.subscribers;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * A content comparison criteria that knows how to compare the content of <code>IStorage</code> and
+ * <code>IRemoteResource</code> objects. The content comparison can be configured to ignore or
+ * consider whitespace.
+ *
+ * @see org.eclipse.team.core.subscribers.ComparisonCriteria
+ */
+public class ContentComparisonCriteria extends ComparisonCriteria {
+
+ private boolean ignoreWhitespace = false;
+
+ final public static String ID_IGNORE_WS = "org.eclipse.team.comparisoncriteria.content.ignore";
+ final public static String ID_DONTIGNORE_WS = "org.eclipse.team.comparisoncriteria.content";
+
+ public String getName() {
+ return "Comparing content" + (ignoreWhitespace ? " ignore whitespace": "");
+ }
+
+ public String getId() {
+ if(ignoreWhitespace) {
+ return ID_IGNORE_WS;
+ } else {
+ return ID_DONTIGNORE_WS;
+ }
+ }
+
+ public ContentComparisonCriteria(ComparisonCriteria[] preConditions, boolean ignoreWhitespace) {
+ super(preConditions);
+ this.ignoreWhitespace = ignoreWhitespace;
+ }
+
+ /**
+ * Helper methods for comparisons that returns true if the resource contents are the same.
+ *
+ * If timestampDiff is true then the timestamps don't differ and there's no point checking the
+ * contents.
+ */
+ public boolean compare(Object e1, Object e2, IProgressMonitor monitor) throws TeamException {
+ try {
+ monitor.beginTask(null, 100);
+ if(checkPreConditions(e1, e2, Policy.subMonitorFor(monitor, 10))) {
+ return true;
+ }
+
+ return contentsEqual(
+ getContents(e1, Policy.subMonitorFor(monitor, 45)),
+ getContents(e2, Policy.subMonitorFor(monitor, 45)),
+ shouldIgnoreWhitespace());
+ } finally {
+ monitor.done();
+ }
+ }
+
+ protected boolean shouldIgnoreWhitespace() {
+ return ignoreWhitespace;
+ }
+
+ /**
+ * Returns <code>true</code> if both input streams byte contents is identical.
+ *
+ * @param input1 first input to contents compare
+ * @param input2 second input to contents compare
+ * @return <code>true</code> if content is equal
+ */
+ private boolean contentsEqual(InputStream is1, InputStream is2, boolean ignoreWhitespace) {
+ if (is1 == is2)
+ return true;
+
+ if (is1 == null && is2 == null) // no byte contents
+ return true;
+
+ try {
+ if (is1 == null || is2 == null) // only one has contents
+ return false;
+
+ while (true) {
+ int c1 = is1.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c1)) c1 = is1.read();
+ int c2 = is2.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c2)) c2 = is2.read();
+ if (c1 == -1 && c2 == -1)
+ return true;
+ if (c1 != c2)
+ break;
+
+ }
+ } catch (IOException ex) {
+ } finally {
+ if (is1 != null) {
+ try {
+ is1.close();
+ } catch (IOException ex) {
+ }
+ }
+ if (is2 != null) {
+ try {
+ is2.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isWhitespace(int c) {
+ if (c == -1) return false;
+ return Character.isWhitespace((char)c);
+ }
+
+ private InputStream getContents(Object resource, IProgressMonitor monitor) throws TeamException {
+ try {
+ if (resource instanceof IStorage) {
+ return new BufferedInputStream(((IStorage) resource).getContents());
+ } else if(resource instanceof IRemoteResource) {
+ IRemoteResource remote = (IRemoteResource)resource;
+ if (!remote.isContainer()) {
+ return new BufferedInputStream(remote.getContents(monitor));
+ }
+ }
+ return null;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ITeamResourceChangeListener.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ITeamResourceChangeListener.java
new file mode 100644
index 000000000..2b943b917
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ITeamResourceChangeListener.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.core.subscribers;
+
+
+import java.util.EventListener;
+
+/**
+ * A resource state change listener is notified of changes to resources
+ * regarding their team state.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @see ITeamManager#addResourceStateChangeListener(IResourceStateChangeListener)
+ */
+public interface ITeamResourceChangeListener extends EventListener{
+
+ /**
+ * Notifies this listener that some resources' team properties have
+ * changed. The changes have already happened. For example, a resource's
+ * base revision may have changed. The resource tree is open for modification
+ * when this method is invoked, so markers can be created, etc.
+ * <p>
+ * Note: This method is called by Team core; it is not intended to be
+ * called directly by clients.
+ * </p>
+ *
+ * @param deltas detailing the kinds of team changes
+ *
+ * [Note: The changed state event is purposely vague. For now it is only
+ * a hint to listeners that they should query the provider to determine the
+ * resources new sync info.]
+ */
+ public void teamResourceChanged(TeamDelta[] deltas);
+}
+
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SyncInfo.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SyncInfo.java
new file mode 100644
index 000000000..6dc6481d3
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SyncInfo.java
@@ -0,0 +1,346 @@
+package org.eclipse.team.core.subscribers;
+
+/*
+ * (c) Copyright IBM Corp. 2000, 2001.
+ * All Rights Reserved.
+ */
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.core.Assert;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * Describes the relative synchronization of a <b>remote</b>
+ * resource and a <b>local</b> resource using a <b>base</b>
+ * resource for comparison.
+ * <p>
+ * Differences between the base and local resources
+ * are classified as <b>outgoing changes</b>; if there is
+ * a difference, the local resource is considered the
+ * <b>outgoing resource</b>.
+ * </p>
+ * <p>
+ * Differences between the base and remote resources
+ * are classified as <b>incoming changes</b>; if there is
+ * a difference, the remote resource is considered the
+ * <b>incoming resource</b>.
+ * </p>
+ * <p>
+ * Differences between the local and remote resources
+ * determine the <b>sync status</b>. The sync status does
+ * not take into account the common resource.
+ * </p>
+ * <p>
+ * Note that under this parse of the world, a resource
+ * can have both incoming and outgoing changes at the
+ * same time, but may nevertheless be in sync!
+ * <p>
+ * [Issue: "Gender changes" are also an interesting aspect...
+ * ]
+ * </p>
+ */
+public class SyncInfo implements IAdaptable {
+
+ /*====================================================================
+ * Constants defining synchronization types:
+ *====================================================================*/
+
+ /**
+ * Sync constant (value 0) indicating element is in sync.
+ */
+ public static final int IN_SYNC = 0;
+
+ /**
+ * Sync constant (value 1) indicating that one side was added.
+ */
+ public static final int ADDITION = 1;
+
+ /**
+ * Sync constant (value 2) indicating that one side was deleted.
+ */
+ public static final int DELETION = 2;
+
+ /**
+ * Sync constant (value 3) indicating that one side was changed.
+ */
+ public static final int CHANGE = 3;
+
+ /**
+ * Bit mask for extracting the change type.
+ */
+ public static final int CHANGE_MASK = CHANGE;
+
+ /*====================================================================
+ * Constants defining synchronization direction:
+ *====================================================================*/
+
+ /**
+ * Sync constant (value 4) indicating a change to the local resource.
+ */
+ public static final int OUTGOING = 4;
+
+ /**
+ * Sync constant (value 8) indicating a change to the remote resource.
+ */
+ public static final int INCOMING = 8;
+
+ /**
+ * Sync constant (value 12) indicating a change to both the remote and local resources.
+ */
+ public static final int CONFLICTING = 12;
+
+ /**
+ * Bit mask for extracting the synchronization direction.
+ */
+ public static final int DIRECTION_MASK = CONFLICTING;
+
+ /*====================================================================
+ * Constants defining synchronization conflict types:
+ *====================================================================*/
+
+ /**
+ * Sync constant (value 16) indication that both the local and remote resources have changed
+ * relative to the base but their contents are the same.
+ */
+ public static final int PSEUDO_CONFLICT = 16;
+
+ /**
+ * Sync constant (value 32) indicating that both the local and remote resources have changed
+ * relative to the base but their content changes do not conflict (e.g. source file changes on different
+ * lines). These conflicts could be merged automatically.
+ */
+ public static final int AUTOMERGE_CONFLICT = 32;
+
+ /**
+ * Sync constant (value 64) indicating that both the local and remote resources have changed relative
+ * to the base and their content changes conflict (e.g. local and remote resource have changes on
+ * same lines). These conflicts can only be correctly resolved by the user.
+ */
+ public static final int MANUAL_CONFLICT = 64;
+
+ /*====================================================================
+ * Members:
+ *====================================================================*/
+ private IResource local;
+ private IRemoteResource base;
+ private IRemoteResource remote;
+ private TeamSubscriber subscriber;
+
+ private int syncKind;
+
+ /**
+ * Construct a sync info object.
+ */
+ public SyncInfo(IResource local, IRemoteResource base, IRemoteResource remote, TeamSubscriber subscriber, IProgressMonitor monitor) throws TeamException {
+ this.local = local;
+ this.base = base;
+ this.remote = remote;
+ this.subscriber = subscriber;
+ this.syncKind = calculateKind(monitor);
+ }
+
+ /**
+ * Returns the state of the local resource. Note that the
+ * resource may or may not exist.
+ *
+ * @return a resource
+ */
+ public IResource getLocal() {
+ return local;
+ }
+
+ /**
+ * Returns the remote resource handle for the base resource,
+ * or <code>null</code> if the base resource does not exist.
+ * <p>
+ * [Note: The type of the common resource may be different from the types
+ * of the local and remote resources.
+ * ]
+ * </p>
+ *
+ * @return a remote resource handle, or <code>null</code>
+ */
+ public IRemoteResource getBase() {
+ return base;
+ }
+
+ /**
+ * Returns the handle for the remote resource,
+ * or <code>null</code> if the remote resource does not exist.
+ * <p>
+ * [Note: The type of the remote resource may be different from the types
+ * of the local and common resources.
+ * ]
+ * </p>
+ *
+ * @return a remote resource handle, or <code>null</code>
+ */
+ public IRemoteResource getRemote() {
+ return remote;
+ }
+
+ /**
+ * Returns the subscriber that created and maintains this sync info
+ * object.
+ */
+ public TeamSubscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /**
+ * Returns the kind of synchronization for this node.
+ * @return
+ */
+ public int getKind() {
+ return syncKind;
+ }
+
+ static public boolean isInSync(int kind) {
+ return kind == IN_SYNC;
+ }
+
+ static public int getDirection(int kind) {
+ return kind & DIRECTION_MASK;
+ }
+
+ static public int getChange(int kind) {
+ return kind & CHANGE_MASK;
+ }
+
+ public boolean equals(Object other) {
+ if(other == this) return true;
+ if(other instanceof SyncInfo) {
+ return getLocal().equals(((SyncInfo)other).getLocal());
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ public Object getAdapter(Class adapter) {
+ if (adapter == IResource.class) {
+ return getLocal();
+ }
+ return null;
+ }
+
+ public String toString() {
+ return getLocal().getName() + " " + kindToString(getKind());
+ }
+
+ public static String kindToString(int kind) {
+ String label = ""; //$NON-NLS-1$
+ if(kind==IN_SYNC) {
+ label = Policy.bind("RemoteSyncElement.insync"); //$NON-NLS-1$
+ } else {
+ switch(kind & DIRECTION_MASK) {
+ case CONFLICTING: label = Policy.bind("RemoteSyncElement.conflicting"); break; //$NON-NLS-1$
+ case OUTGOING: label = Policy.bind("RemoteSyncElement.outgoing"); break; //$NON-NLS-1$
+ case INCOMING: label = Policy.bind("RemoteSyncElement.incoming"); break; //$NON-NLS-1$
+ }
+ switch(kind & CHANGE_MASK) {
+ case CHANGE: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.change")); break; //$NON-NLS-1$ //$NON-NLS-2$
+ case ADDITION: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.addition")); break; //$NON-NLS-1$ //$NON-NLS-2$
+ case DELETION: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.deletion")); break; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if((kind & MANUAL_CONFLICT) != 0) {
+ label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.manual")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if((kind & AUTOMERGE_CONFLICT) != 0) {
+ label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.auto")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ return Policy.bind("RemoteSyncElement.delimit", label); //$NON-NLS-1$
+ }
+
+ protected int calculateKind(IProgressMonitor progress) throws TeamException {
+ progress = Policy.monitorFor(progress);
+ int description = IN_SYNC;
+
+ ComparisonCriteria criteria = subscriber.getCurrentComparisonCriteria();
+
+ boolean localExists = local.exists();
+
+ if (subscriber.isThreeWay()) {
+ if (base == null) {
+ if (remote == null) {
+ if (!localExists) {
+ description = IN_SYNC;
+ } else {
+ description = OUTGOING | ADDITION;
+ }
+ } else {
+ if (!localExists) {
+ description = INCOMING | ADDITION;
+ } else {
+ description = CONFLICTING | ADDITION;
+ try {
+ progress.beginTask(null, 60);
+ if (criteria.compare(local, remote, Policy.subMonitorFor(progress, 30))) {
+ description |= PSEUDO_CONFLICT;
+ }
+ } finally {
+ progress.done();
+ }
+ }
+ }
+ } else {
+ if (!localExists) {
+ if (remote == null) {
+ description = CONFLICTING | DELETION | PSEUDO_CONFLICT;
+ } else {
+ if (criteria.compare(base, remote, progress))
+ description = OUTGOING | DELETION;
+ else
+ description = CONFLICTING | CHANGE;
+ }
+ } else {
+ if (remote == null) {
+ if (criteria.compare(local, base, progress))
+ description = INCOMING | DELETION;
+ else
+ description = CONFLICTING | CHANGE;
+ } else {
+ progress.beginTask(null, 90);
+ boolean ay = criteria.compare(local, base, Policy.subMonitorFor(progress, 30));
+ boolean am = criteria.compare(base, remote, Policy.subMonitorFor(progress, 30));
+ if (ay && am) {
+ ;
+ } else if (ay && !am) {
+ description = INCOMING | CHANGE;
+ } else if (!ay && am) {
+ description = OUTGOING | CHANGE;
+ } else {
+ if(! criteria.compare(local, remote, Policy.subMonitorFor(progress, 30))) {
+ description = CONFLICTING | CHANGE;
+ }
+ }
+ progress.done();
+ }
+ }
+ }
+ } else { // two compare without access to base contents
+ if (remote == null) {
+ if (!localExists) {
+ Assert.isTrue(false);
+ // shouldn't happen
+ } else {
+ description= DELETION;
+ }
+ } else {
+ if (!localExists) {
+ description= ADDITION;
+ } else {
+ if (! criteria.compare(local, remote, Policy.subMonitorFor(progress, 30)))
+ description= CHANGE;
+ }
+ }
+ }
+ return description;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/TeamSubscriberFactory.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/TeamSubscriberFactory.java
new file mode 100644
index 000000000..ba14fbd97
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/TeamSubscriberFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.core.subscribers;
+
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.core.SaveContext;
+
+/**
+ * A subscriber factory is responsible for saving and restoring subscribers. Implementations must
+ * provide a public no-arg constructor.
+ *
+ * Example extension point for registering a subscriber factory:
+ *
+ * <extension point="org.eclipse.team.core.subscriber">
+ * <subscriber class="org.eclipse.team.internal.webdav.DavSubscriberFactory"/>
+ *</extension>
+ *
+ * @see org.eclipse.team.core.subscribers.TeamSubscriber
+ */
+abstract public class TeamSubscriberFactory {
+
+ /**
+ * A subscriber factory id identifies the factory type and the type of it's subscribers. Subscribers
+ * created via a specific factory should return a qualified name from TeamSubscriber#getID() that
+ * matches the id of their factory.
+ * <p>
+ * For example, a WebDav subscriber factory would have "org.eclipse.team.webdav.subscriber" as
+ * its id. Subsequent WebDav subscribers must construct their id based on this qualifier.
+ *
+ * @return the factory's id
+ */
+ abstract public String getID();
+
+ /**
+ * Called to save the state of the given subscriber. The saved state should contain enough
+ * information so that a subcriber can be recreated from the returned <code>SaveContext</code>.
+ * A subscriber that doesn't have information to the saved should return <code>null</code>.
+ * <p>
+ * This may be called during workspace snapshot or at shutdown.
+ * </p>
+ *
+ * @return a save context containing the state of this subscriber
+ * @throws TeamException if there was a problem creating the save context.
+ */
+ abstract public SaveContext saveSubscriber(TeamSubscriber subscriber) throws TeamException;
+
+ /**
+ * Called to restore a subscriber with <code>id</code> from a given <code>SaveContext</code>. This is
+ * used to restore subscribers between workbench sessions.
+ *
+ * @return a subscriber instance
+ * @throws TeamException if there was a problem restoring from the save context.
+ */
+ abstract public TeamSubscriber restoreSubscriber(QualifiedName id, SaveContext saveContext) throws TeamException;
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContext.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContext.java
new file mode 100644
index 000000000..8f4f1da70
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContext.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Something (as a mark of visible sign) left by a material thing formely present but now
+ * lost or unknown.
+ *
+ * TODO: API doc if this class is to remain. Ideally it should replaced by a core mechanism which
+ * allows persisting somewhat like IMemento.
+ */
+public class SaveContext {
+
+ private String name;
+
+ private String value;
+
+ private Map attributes;
+
+ private List children = new ArrayList(2);
+
+ public SaveContext() {}
+
+ public String getAttribute(String name) {
+ if(attributes == null) {
+ return null;
+ }
+ return (String)attributes.get(name);
+ }
+
+ public void putInteger(String key, int n) {
+ addAttribute(key, String.valueOf(n));
+ }
+
+ public void putFloat(String key, float n) {
+ addAttribute(key, String.valueOf(n));
+ }
+
+ public void putString(String key, String n) {
+ addAttribute(key, n);
+ }
+
+ public void putBoolean(String key, boolean n) {
+ addAttribute(key, String.valueOf(n));
+ }
+
+ public int getInteger(String key) {
+ String f = getAttribute(key);
+ if(f != null) {
+ return new Integer(f).intValue();
+ }
+ return 0;
+ }
+
+ public float getFloat(String key) {
+ String f = getAttribute(key);
+ if(f != null) {
+ return new Float(f).floatValue();
+ }
+ return 0;
+ }
+
+ public String getString(String key) {
+ return getAttribute(key);
+ }
+
+ public boolean getBoolean(String key) {
+ String bool = getAttribute(key);
+ if(bool != null) {
+ return bool.equals("true") ? true : false;
+ }
+ return true;
+ }
+
+ public String[] getAttributeNames() {
+ if(attributes == null) {
+ return new String[0];
+ }
+ return (String[])attributes.keySet().toArray(new String[attributes.keySet().size()]);
+ }
+
+ public SaveContext[] getChildren() {
+ return (SaveContext[]) children.toArray(new SaveContext[children.size()]);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setAttributes(Map map) {
+ attributes = map;
+ }
+
+ public void setChildren(SaveContext[] items) {
+ children = new ArrayList(Arrays.asList(items));
+ }
+
+ public void putChild(SaveContext child) {
+ children.add(child);
+ }
+
+ public void setName(String string) {
+ name = string;
+ }
+
+ public void setValue(String string) {
+ value = string;
+ }
+
+ public void addAttribute(String key, String value) {
+ if(attributes == null) {
+ attributes = new HashMap();
+ }
+ attributes.put(key, value);
+ }
+
+ public String toString() {
+ return getName() + " ->" + attributes.toString();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLContentHandler.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLContentHandler.java
new file mode 100644
index 000000000..71fdc49fc
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLContentHandler.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SaveContextXMLContentHandler
+ */
+public class SaveContextXMLContentHandler extends DefaultHandler {
+
+ private StringBuffer buffer = new StringBuffer();
+ private Stack contextStack = new Stack();
+ private SaveContext last;
+ private Map children = new HashMap();
+
+ public SaveContextXMLContentHandler() {
+ }
+
+ /**
+ * @see ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] chars, int startIndex, int length) throws SAXException {
+ buffer.append(chars, startIndex, length);
+ }
+
+ /**
+ * @see ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ SaveContext ctx = (SaveContext)contextStack.peek();
+ if (!localName.equals(ctx.getName())) {
+ // keep going
+ } else {
+ last = (SaveContext)contextStack.pop();
+ if(! contextStack.isEmpty()) {
+ SaveContext parent = (SaveContext)contextStack.peek();
+ parent.putChild(ctx);
+ }
+ }
+ }
+
+ /**
+ * @see ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ SaveContext context = new SaveContext();
+ context.setName(localName);
+ for (int i = 0; i < atts.getLength(); i++) {
+ String attrName = atts.getLocalName(i);
+ String attrValue = atts.getValue(i);
+ context.putString(attrName, attrValue);
+ }
+ // empty buffer
+ buffer = new StringBuffer();
+ contextStack.push(context);
+ }
+
+ public SaveContext getSaveContext() {
+ return last;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLWriter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLWriter.java
new file mode 100644
index 000000000..0159d6dc3
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/SaveContextXMLWriter.java
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import org.apache.xerces.parsers.SAXParser;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.team.core.TeamException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class SaveContextXMLWriter extends PrintWriter {
+ protected int tab;
+
+ /* constants */
+ protected static final String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; //$NON-NLS-1$
+
+ public SaveContextXMLWriter(OutputStream output) throws UnsupportedEncodingException {
+ super(new OutputStreamWriter(output, "UTF8")); //$NON-NLS-1$
+ tab = 0;
+ println(XML_VERSION);
+ }
+ public void endTag(String name) {
+ tab--;
+ printTag('/' + name, null);
+ }
+ public void printSimpleTag(String name, Object value) {
+ if (value != null) {
+ printTag(name, null, true, false);
+ print(getEscaped(String.valueOf(value)));
+ printTag('/' + name, null, false, true);
+ }
+ }
+ public void printTabulation() {
+ for (int i = 0; i < tab; i++)
+ super.print('\t');
+ }
+ private void printTag(String name, HashMap parameters) {
+ printTag(name, parameters, true, true);
+ }
+ private void printTag(String name, HashMap parameters, boolean tab, boolean newLine) {
+ printTag(name, parameters, tab, newLine, false);
+ }
+ private void printTag(String name, HashMap parameters, boolean tab, boolean newLine, boolean end) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("<"); //$NON-NLS-1$
+ sb.append(name);
+ if (parameters != null)
+ for (Enumeration enum = Collections.enumeration(parameters.keySet()); enum.hasMoreElements();) {
+ sb.append(" "); //$NON-NLS-1$
+ String key = (String) enum.nextElement();
+ sb.append(key);
+ sb.append("=\""); //$NON-NLS-1$
+ sb.append(getEscaped(String.valueOf(parameters.get(key))));
+ sb.append("\""); //$NON-NLS-1$
+ }
+ if (end)
+ sb.append('/');
+ sb.append(">"); //$NON-NLS-1$
+ if (tab)
+ printTabulation();
+ if (newLine)
+ println(sb.toString());
+ else
+ print(sb.toString());
+ }
+ public void startTag(String name, HashMap parameters) {
+ startTag(name, parameters, true);
+ }
+ public void startTag(String name, HashMap parameters, boolean newLine) {
+ printTag(name, parameters, true, newLine);
+ tab++;
+ }
+ public void startAndEndTag(String name, HashMap parameters, boolean newLine) {
+ printTag(name, parameters, true, true, true);
+ }
+ private static void appendEscapedChar(StringBuffer buffer, char c) {
+ String replacement = getReplacement(c);
+ if (replacement != null) {
+ buffer.append('&');
+ buffer.append(replacement);
+ buffer.append(';');
+ } else {
+ buffer.append(c);
+ }
+ }
+ public static String getEscaped(String s) {
+ StringBuffer result = new StringBuffer(s.length() + 10);
+ for (int i = 0; i < s.length(); ++i)
+ appendEscapedChar(result, s.charAt(i));
+ return result.toString();
+ }
+ private static String getReplacement(char c) {
+ // Encode special XML characters into the equivalent character references.
+ // These five are defined by default for all XML documents.
+ switch (c) {
+ case '<' :
+ return "lt"; //$NON-NLS-1$
+ case '>' :
+ return "gt"; //$NON-NLS-1$
+ case '"' :
+ return "quot"; //$NON-NLS-1$
+ case '\'' :
+ return "apos"; //$NON-NLS-1$
+ case '&' :
+ return "amp"; //$NON-NLS-1$
+ }
+ return null;
+ }
+ public void write(SaveContext item) {
+
+ // start tag for this element
+ String name = item.getName();
+ String value = item.getValue();
+ String[] attributeNames = item.getAttributeNames();
+ HashMap attributes = new HashMap(attributeNames.length);
+ for (int i = 0; i < attributeNames.length; i++) {
+ String attrName = attributeNames[i];
+ attributes.put(attrName, item.getAttribute(attrName));
+ }
+ startTag(name, attributes);
+
+ // write out child elements
+ SaveContext[] children = item.getChildren();
+ if(children != null) {
+ for (int i = 0; i < children.length; i++) {
+ SaveContext child = children[i];
+ write(child);
+ }
+ }
+
+ // value
+ if(value != null) {
+ println(value);
+ }
+
+ // end tag for this element
+ endTag(name);
+ }
+
+ static public void writeXMLPluginMetaFile(Plugin plugin, String filename, SaveContext element) throws TeamException {
+ IPath pluginStateLocation = plugin.getStateLocation();
+ File tempFile = pluginStateLocation.append(filename + ".tmp").toFile(); //$NON-NLS-1$
+ File stateFile = pluginStateLocation.append(filename).toFile();
+ try {
+ SaveContextXMLWriter writer = new SaveContextXMLWriter(new BufferedOutputStream(new FileOutputStream(tempFile)));
+ try {
+ writer.write(element);
+ } finally {
+ writer.close();
+ }
+ if (stateFile.exists()) {
+ stateFile.delete();
+ }
+ boolean renamed = tempFile.renameTo(stateFile);
+ if (!renamed) {
+ throw new TeamException(new Status(Status.ERROR, TeamPlugin.ID, TeamException.UNABLE, Policy.bind("RepositoryManager.rename", tempFile.getAbsolutePath()), null));
+ }
+ } catch (IOException e) {
+ throw new TeamException(new Status(Status.ERROR, TeamPlugin.ID, TeamException.UNABLE, Policy.bind("RepositoryManager.save",stateFile.getAbsolutePath()), e));
+ }
+ }
+
+ static public SaveContext readXMLPluginMetaFile(Plugin plugin, String filename) throws TeamException {
+ BufferedInputStream is = null;
+ try {
+ IPath pluginStateLocation = plugin.getStateLocation();
+ File file = pluginStateLocation.append(filename).toFile(); //$NON-NLS-1$
+ if (file.exists()) {
+ is = new BufferedInputStream(new FileInputStream(file));
+ SAXParser parser = new SAXParser();
+ SaveContextXMLContentHandler handler = new SaveContextXMLContentHandler();
+ parser.setContentHandler(handler);
+ parser.parse(new InputSource(is));
+ return handler.getSaveContext();
+ }
+ return null;
+ } catch (SAXException ex) {
+ throw new TeamException(Policy.bind("RepositoryManager.ioException"), ex);
+ } catch (IOException e) {
+ throw new TeamException(Policy.bind("RepositoryManager.ioException"), e);
+ } finally {
+ if(is != null) {
+ try {
+ is.close();
+ } catch (IOException e1) {
+ throw new TeamException(Policy.bind("RepositoryManager.ioException"), e1);
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSRevisionNumberCompareCriteria.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSRevisionNumberCompareCriteria.java
new file mode 100644
index 000000000..311203e5c
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSRevisionNumberCompareCriteria.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.subscribers.ComparisonCriteria;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
+
+/**
+ * CVSRevisionNumberCompareCriteria
+ */
+ public class CVSRevisionNumberCompareCriteria extends ComparisonCriteria {
+
+ /* (non-Javadoc)
+ * @see ComparisonCriteria#getName()
+ */
+ public String getName() {
+ return "Revision number comparison";
+ }
+
+ /* (non-Javadoc)
+ * @see ComparisonCriteria#getId()
+ */
+ public String getId() {
+ return "org.eclipse.team.cvs.revisioncomparator";
+ }
+
+ /* (non-Javadoc)
+ * @see ComparisonCriteria#compare(Object, Object, IProgressMonitor)
+ */
+ public boolean compare(Object e1, Object e2, IProgressMonitor monitor) {
+ if(e1 instanceof IResource && e2 instanceof IRemoteResource) {
+ return compare((IResource)e1, (IRemoteResource)e2);
+ } else if(e1 instanceof IRemoteResource && e2 instanceof IRemoteResource) {
+ return compare((IRemoteResource)e1, (IRemoteResource)e2);
+ }
+ return false;
+ }
+
+ /**
+ * @see RemoteSyncElement#timestampEquals(IRemoteResource, IRemoteResource)
+ */
+ protected boolean compare(IRemoteResource e1, IRemoteResource e2) {
+ if(e1.isContainer()) {
+ if(e2.isContainer()) {
+ return true;
+ }
+ return false;
+ }
+ return e1.equals(e2);
+ }
+
+ /**
+ * @see RemoteSyncElement#timestampEquals(IResource, IRemoteResource)
+ */
+ protected boolean compare(IResource e1, IRemoteResource e2) {
+ if(e1.getType() != IResource.FILE) {
+ if(e2.isContainer()) {
+ return true;
+ }
+ return false;
+ }
+ ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor((IFile)e1);
+ try {
+ byte[] syncBytes1 = cvsFile.getSyncBytes();
+ byte[] syncBytes2 = ((ICVSRemoteFile)e2).getSyncBytes();
+
+ if(syncBytes1 != null) {
+ if(ResourceSyncInfo.isDeletion(syncBytes1) || ResourceSyncInfo.isMerge(syncBytes1) || cvsFile.isModified(null)) {
+ return false;
+ }
+ return ResourceSyncInfo.getRevision(syncBytes1).equals(ResourceSyncInfo.getRevision(syncBytes2));
+ }
+ return false;
+ } catch(CVSException e) {
+ CVSProviderPlugin.log(e.getStatus());
+ return false;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncTreeSubscriber.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncTreeSubscriber.java
new file mode 100644
index 000000000..8a06bbc1f
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSSyncTreeSubscriber.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.RepositoryProvider;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.ComparisonCriteria;
+import org.eclipse.team.core.subscribers.ContentComparisonCriteria;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.core.subscribers.TeamDelta;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSynchronizer;
+
+/**
+ * This class provides common funtionality for three way sychronizing
+ * for CVS.
+ */
+public abstract class CVSSyncTreeSubscriber extends TeamSubscriber {
+
+ private QualifiedName id;
+ private String name;
+ private String description;
+
+ // options this subscriber supports for determining the sync state of resources
+ private Map comparisonCriterias = new HashMap();
+ private String defaultCriteria;
+
+ CVSSyncTreeSubscriber(QualifiedName id, String name, String description) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ initializeComparisonCriteria();
+ }
+
+ /**
+ * Method invoked from the constructor to initialize the comparison criteria
+ * and the default criteria.
+ * This method can be overriden by subclasses.
+ */
+ protected void initializeComparisonCriteria() {
+ // setup comparison criteria
+ ComparisonCriteria revisionNumberComparator = new CVSRevisionNumberCompareCriteria();
+ ComparisonCriteria contentsComparator = new ContentComparisonCriteria(new ComparisonCriteria[] {revisionNumberComparator}, false /*consider whitespace */);
+ ComparisonCriteria contentsComparatorIgnoreWhitespace = new ContentComparisonCriteria(new ComparisonCriteria[] {revisionNumberComparator}, true /* ignore whitespace */);
+
+ addComparisonCriteria(revisionNumberComparator);
+ addComparisonCriteria(contentsComparator);
+ addComparisonCriteria(contentsComparatorIgnoreWhitespace);
+
+ // default
+ defaultCriteria = revisionNumberComparator.getId();
+ }
+
+ /**
+ * Add the comparison criteria to the subscriber
+ *
+ * @param comparator
+ */
+ protected void addComparisonCriteria(ComparisonCriteria comparator) {
+ comparisonCriterias.put(comparator.getId(), comparator);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getId()
+ */
+ public QualifiedName getId() {
+ return id;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getName()
+ */
+ public String getName() {
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getDescription()
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) throws TeamException {
+ if(resource.getType() == IResource.FILE) {
+ return new IResource[0];
+ }
+ try {
+ // Filter and return only phantoms associated with the remote synchronizer.
+ IResource[] members = ((IContainer)resource).members(true /* include phantoms */);
+ List filteredMembers = new ArrayList(members.length);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+
+ // TODO: consider that there may be several sync states on this resource. There
+ // should instead be a method to check for the existance of a set of sync types on
+ // a resource.
+ if(member.isPhantom() && getRemoteSynchronizer().getSyncBytes(member) == null) {
+ continue;
+ }
+
+ // TODO: Is this a valid use of isSupervised
+ if (isSupervised(resource)) {
+ filteredMembers.add(member);
+ }
+ }
+ return (IResource[]) filteredMembers.toArray(new IResource[filteredMembers.size()]);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#roots()
+ */
+ public IResource[] roots() throws TeamException {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getRemoteResource(org.eclipse.core.resources.IResource)
+ */
+ public IRemoteResource getRemoteResource(IResource resource) throws TeamException {
+ return getRemoteSynchronizer().getRemoteResource(resource);
+ }
+
+ public IRemoteResource getBaseResource(IResource resource) throws TeamException {
+ return getBaseSynchronizer().getRemoteResource(resource);
+ }
+
+ /**
+ * Return the synchronizer that provides the remote resources
+ */
+ protected abstract ResourceSynchronizer getRemoteSynchronizer();
+ /**
+ * Return the synchronizer that provides the base resources
+ */
+ protected abstract ResourceSynchronizer getBaseSynchronizer();
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getSyncInfo(org.eclipse.core.resources.IResource)
+ */
+ public SyncInfo getSyncInfo(IResource resource, IProgressMonitor monitor) throws TeamException {
+ if (!isSupervised(resource)) return null;
+ IRemoteResource remoteResource = getRemoteResource(resource);
+ if(resource.getType() == IResource.FILE) {
+ IRemoteResource baseResource = getBaseResource(resource);
+ return getSyncInfo(resource, baseResource, remoteResource, monitor);
+ } else {
+ // In CVS, folders do not have a base. Hence, the remote is used as the base.
+ return getSyncInfo(resource, remoteResource, remoteResource, monitor);
+ }
+ }
+
+ /**
+ * Method that creates an instance of SyncInfo for the provider local, base and remote.
+ * Can be overiden by subclasses.
+ * @param local
+ * @param base
+ * @param remote
+ * @param monitor
+ * @return
+ */
+ protected SyncInfo getSyncInfo(IResource local, IRemoteResource base, IRemoteResource remote, IProgressMonitor monitor) throws TeamException {
+ return new CVSSyncInfo(local, base, remote, this, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#refresh(org.eclipse.core.resources.IResource[], int, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ IResource[] remoteChanges = getRemoteSynchronizer().refresh(resources, depth, monitor);
+ IResource[] baseChanges = getBaseSynchronizer().refresh(resources, depth, monitor);
+
+ Set allChanges = new HashSet();
+ allChanges.addAll(Arrays.asList(remoteChanges));
+ allChanges.addAll(Arrays.asList(baseChanges));
+ IResource[] changedResources = (IResource[]) allChanges.toArray(new IResource[allChanges.size()]);
+ fireTeamResourceChange(TeamDelta.asSyncChangedDeltas(this, changedResources));
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getCurrentComparisonCriteria()
+ */
+ public ComparisonCriteria getCurrentComparisonCriteria() {
+ return (ComparisonCriteria)comparisonCriterias.get(defaultCriteria);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#setCurrentComparisonCriteria(java.lang.String)
+ */
+ public void setCurrentComparisonCriteria(String id) throws TeamException {
+ if(! comparisonCriterias.containsKey(id)) {
+ throw new CVSException(id + " is not a valid comparison criteria");
+ }
+ this.defaultCriteria = id;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#getComparisonCriterias()
+ */
+ public ComparisonCriteria[] getComparisonCriterias() {
+ return (ComparisonCriteria[]) comparisonCriterias.values().toArray(new ComparisonCriteria[comparisonCriterias.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#isSupervised(org.eclipse.core.resources.IResource)
+ */
+ public boolean isSupervised(IResource resource) throws TeamException {
+ RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId());
+ if (provider == null) return false;
+ // TODO: what happens for resources that don't exist?
+ // TODO: is it proper to use ignored here?
+ ICVSResource cvsThing = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ if (cvsThing.isIgnored()) {
+ // An ignored resource could have an incoming addition (conflict)
+ return getRemoteSynchronizer().getSyncBytes(resource) != null;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#isThreeWay()
+ */
+ public boolean isThreeWay() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#isCancellable()
+ */
+ public boolean isCancellable() {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#cancel()
+ */
+ public void cancel() {
+ // noop
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java
new file mode 100644
index 000000000..d4cccb83d
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/CVSWorkspaceSubscriber.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.RepositoryProvider;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamDelta;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.core.syncinfo.OptimizedRemoteSynchronizer;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSynchronizer;
+
+/**
+ * CVSWorkspaceSubscriber
+ */
+public class CVSWorkspaceSubscriber extends CVSSyncTreeSubscriber implements IResourceStateChangeListener {
+
+ private OptimizedRemoteSynchronizer remoteSynchronizer;
+
+ // qualified name for remote sync info
+ private static final String REMOTE_RESOURCE_KEY = "remote-resource-key";
+
+ CVSWorkspaceSubscriber(QualifiedName id, String name, String description) {
+ super(id, name, description);
+
+ // install sync info participant
+ remoteSynchronizer = new OptimizedRemoteSynchronizer(REMOTE_RESOURCE_KEY);
+
+ // TODO: temporary proxy for CVS events
+ CVSProviderPlugin.addResourceStateChangeListener(this);
+ }
+
+ /*
+ * Return the list of projects shared with a CVS team provider.
+ *
+ * [Issue : this will have to change when folders can be shared with
+ * a team provider instead of the current project restriction]
+ * (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ISyncTreeSubscriber#roots()
+ */
+ public IResource[] roots() throws TeamException {
+ List result = new ArrayList();
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ if(project.isOpen()) {
+ RepositoryProvider provider = RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId());
+ if(provider != null) {
+ result.add(project);
+ }
+ }
+ }
+ return (IProject[]) result.toArray(new IProject[result.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.IResourceStateChangeListener#resourceSyncInfoChanged(org.eclipse.core.resources.IResource[])
+ */
+ public void resourceSyncInfoChanged(IResource[] changedResources) {
+
+ // TODO: hack for clearing the remote state when anything to the resource
+ // sync is changed. Should be able to set the *right* remote/base based on
+ // the sync being set.
+ // TODO: This will throw exceptions if performed during the POST_CHANGE delta phase!!!
+ for (int i = 0; i < changedResources.length; i++) {
+ IResource resource = changedResources[i];
+ try {
+ // TODO should use revision and tag to determine if remote is stale
+ // TODO outgoing deletions would require special handling
+ if (resource.getType() == IResource.FILE
+ && (resource.exists() || resource.isPhantom())) {
+ remoteSynchronizer.removeSyncBytes(resource, IResource.DEPTH_ZERO);
+ } else if (resource.getType() == IResource.FOLDER) {
+ // If the base has sync info for the folder, purge the remote bytes
+ if (getBaseSynchronizer().getSyncBytes(resource) != null) {
+ remoteSynchronizer.removeSyncBytes(resource, IResource.DEPTH_ZERO);
+ }
+ }
+ } catch (CVSException e) {
+ CVSProviderPlugin.log(e);
+ }
+ }
+
+ fireTeamResourceChange(TeamDelta.asSyncChangedDeltas(this, changedResources));
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.IResourceStateChangeListener#resourceModified(org.eclipse.core.resources.IResource[])
+ */
+ public void resourceModified(IResource[] changedResources) {
+ // TODO: This is only ever called from a delta POST_CHANGE
+ // which causes problems since the workspace tree is closed
+ // for modification and we flush the sync info in resourceSyncInfoChanged
+
+ // Since the listeners of the Subscriber will also listen to deltas
+ // we don't need to propogate this.
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.IResourceStateChangeListener#projectConfigured(org.eclipse.core.resources.IProject)
+ */
+ public void projectConfigured(IProject project) {
+ TeamDelta delta = new TeamDelta(this, TeamDelta.PROVIDER_CONFIGURED, project);
+ fireTeamResourceChange(new TeamDelta[] {delta});
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.IResourceStateChangeListener#projectDeconfigured(org.eclipse.core.resources.IProject)
+ */
+ public void projectDeconfigured(IProject project) {
+ try {
+ remoteSynchronizer.removeSyncBytes(project, IResource.DEPTH_INFINITE);
+ } catch (CVSException e) {
+ CVSProviderPlugin.log(e);
+ }
+ TeamDelta delta = new TeamDelta(this, TeamDelta.PROVIDER_DECONFIGURED, project);
+ fireTeamResourceChange(new TeamDelta[] {delta});
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getRemoteSynchronizer()
+ */
+ protected ResourceSynchronizer getRemoteSynchronizer() {
+ return remoteSynchronizer;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.CVSSyncTreeSubscriber#getBaseSynchronizer()
+ */
+ protected ResourceSynchronizer getBaseSynchronizer() {
+ return remoteSynchronizer.getBaseSynchronizer();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.TeamSubscriber#getAllOutOfSync(org.eclipse.core.resources.IResource[], int, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public SyncInfo[] getAllOutOfSync(IResource[] resources, final int depth, IProgressMonitor monitor) throws TeamException {
+ monitor.beginTask(null, resources.length * 100);
+ final List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ final IProgressMonitor infinite = Policy.infiniteSubMonitorFor(monitor, 100);
+ try {
+ infinite.beginTask(null, 512);
+ resource.accept(new IResourceVisitor() {
+ public boolean visit(IResource resource) throws CoreException {
+ try {
+ if (isOutOfSync(resource, infinite)) {
+ SyncInfo info = getSyncInfo(resource, infinite);
+ if (info != null && info.getKind() != 0) {
+ result.add(info);
+ }
+ }
+ return true;
+ } catch (TeamException e) {
+ // TODO: This is probably not the right thing to do here
+ throw new CoreException(e.getStatus());
+ }
+ }
+ }, depth, true /* include phantoms */);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ } finally {
+ infinite.done();
+ }
+ }
+ monitor.done();
+ return (SyncInfo[]) result.toArray(new SyncInfo[result.size()]);
+ }
+
+ private boolean isOutOfSync(IResource resource, IProgressMonitor monitor) throws CVSException {
+ return (hasIncomingChange(resource) || hasOutgoingChange(CVSWorkspaceRoot.getCVSResourceFor(resource), monitor));
+ }
+
+ private boolean hasOutgoingChange(ICVSResource resource, IProgressMonitor monitor) throws CVSException {
+ if (resource.isFolder()) {
+ // A folder is an outgoing change if it is not a CVS folder and not ignored
+ ICVSFolder folder = (ICVSFolder)resource;
+ // TODO: The parent caches the dirty state so we only need to check
+ // the file if the parent is dirty.
+ // TODO: Unfortunately, the modified check on the parent still loads
+ // the CVS folder information so not much is gained
+ if (folder.getParent().isModified(monitor)) {
+ return !folder.isCVSFolder() && !folder.isIgnored();
+ }
+ } else {
+ // A file is an outgoing change if it is modified
+ ICVSFile file = (ICVSFile)resource;
+ // TODO: The parent chaches the dirty state so we only need to check
+ // the file if the parent is dirty
+ if (file.getParent().isModified(monitor)) {
+ return file.isModified(monitor);
+ }
+ }
+ return false;
+ }
+
+ private boolean hasIncomingChange(IResource resource) throws CVSException {
+ return remoteSynchronizer.getRemoteBytes(resource) != null;
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/UpdateMergableOnly.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/UpdateMergableOnly.java
new file mode 100644
index 000000000..7abf1697c
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/UpdateMergableOnly.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.client;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.ICVSFile;
+import org.eclipse.team.internal.ccvs.core.ICVSFolder;
+import org.eclipse.team.internal.ccvs.core.client.listeners.ICommandOutputListener;
+import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
+
+/**
+ * This custom update command will only update files that
+ * are either incoming changes (Update-existing) or auto-mergable
+ * (Merged with no "+=" in entry line).
+ */
+public class UpdateMergableOnly extends Update {
+
+ List skippedFiles = new ArrayList();
+
+ public class MergableOnlyUpdatedHandler extends UpdatedHandler {
+
+ public MergableOnlyUpdatedHandler() {
+ // handle "Merged" responses
+ super(UpdatedHandler.HANDLE_MERGED);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.client.UpdatedHandler#getTargetFile(org.eclipse.team.internal.ccvs.core.ICVSFolder, java.lang.String, byte[])
+ */
+ protected ICVSFile getTargetFile(ICVSFolder mParent, String fileName, byte[] entryBytes) throws CVSException {
+ String adjustedFileName = fileName;
+ if (ResourceSyncInfo.isMergedWithConflicts(entryBytes)) {
+ // for merged-with-conflict, return a temp file
+ adjustedFileName = ".##" + adjustedFileName;
+ skippedFiles.add(((IContainer)mParent.getIResource()).getFile(new Path(fileName)));
+ }
+ return super.getTargetFile(mParent, fileName, entryBytes);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.client.UpdatedHandler#receiveTargetFile(org.eclipse.team.internal.ccvs.core.client.Session, org.eclipse.team.internal.ccvs.core.ICVSFile, java.lang.String, java.util.Date, boolean, boolean, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void receiveTargetFile(
+ Session session,
+ ICVSFile mFile,
+ String entryLine,
+ Date modTime,
+ boolean binary,
+ boolean readOnly,
+ IProgressMonitor monitor)
+ throws CVSException {
+
+ if (ResourceSyncInfo.isMergedWithConflicts(entryLine.getBytes())) {
+ // For merged-with-conflict, just recieve the file contents.
+ // Use the Updated handler type so that the file will be created or
+ // updated.
+ session.receiveFile(mFile, binary, UpdatedHandler.HANDLE_UPDATED, monitor);
+ } else {
+ super.receiveTargetFile(session, mFile, entryLine, modTime, binary, readOnly, monitor);
+ }
+ }
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.client.Command#doExecute(org.eclipse.team.internal.ccvs.core.client.Session, org.eclipse.team.internal.ccvs.core.client.Command.GlobalOption[], org.eclipse.team.internal.ccvs.core.client.Command.LocalOption[], java.lang.String[], org.eclipse.team.internal.ccvs.core.client.listeners.ICommandOutputListener, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected IStatus doExecute(
+ Session session,
+ GlobalOption[] globalOptions,
+ LocalOption[] localOptions,
+ String[] arguments,
+ ICommandOutputListener listener,
+ IProgressMonitor monitor)
+ throws CVSException {
+
+ MergableOnlyUpdatedHandler newHandler = new MergableOnlyUpdatedHandler();
+ ResponseHandler oldHandler = getResponseHandler(newHandler.getResponseID());
+ skippedFiles.clear();
+ try {
+ registerResponseHandler(newHandler);
+ return super.doExecute(
+ session,
+ globalOptions,
+ localOptions,
+ arguments,
+ listener,
+ monitor);
+ } finally {
+ registerResponseHandler(oldHandler);
+ }
+ }
+
+ public IFile[] getSkippedFiles() {
+ return (IFile[]) skippedFiles.toArray(new IFile[skippedFiles.size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java
index 13918e4fd..04d428656 100644
--- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java
@@ -124,7 +124,9 @@ public class EclipseFile extends EclipseResource implements ICVSFile {
// ignore the monitor, there is no valuable progress to be shown when
// calculating the dirty state for files. It is relatively fast.
- if (!exists()) return true;
+ if (!exists()) {
+ return getSyncBytes() != null;
+ }
int state = EclipseSynchronizer.getInstance().getModificationState(getIFile());
if (state != UNKNOWN) {
@@ -145,12 +147,16 @@ public class EclipseFile extends EclipseResource implements ICVSFile {
* info.
*/
private boolean computeModified(ResourceSyncInfo info) throws CVSException {
- if (info == null) return true;
+ // if there is no sync info and it doesn't exist then it is a phantom we don't care
+ // about.
+ if (info == null) {
+ return exists();
+ }
// isMerged() must be called because when a file is updated and merged by the cvs server the timestamps
// are equal. Merged files should however be reported as dirty because the user should take action and commit
// or review the merged contents.
- if(info.isMerged() || !exists()) return true;
+ if(info.isAdded() || info.isMerged() || !exists()) return true;
return !getTimeStamp().equals(info.getTimeStamp());
}
@@ -435,9 +441,11 @@ public class EclipseFile extends EclipseResource implements ICVSFile {
Date timeStamp = oldInfo.getTimeStamp();
if (timeStamp == null || oldInfo.isMergedWithConflicts()) {
// If the entry line has no timestamp, put the file timestamp in the entry line
- MutableResourceSyncInfo mutable = oldInfo.cloneMutable();
- mutable.setTimeStamp(getTimeStamp(), true /* clear merged */);
- newInfo = mutable;
+ if(! oldInfo.isAdded()) {
+ MutableResourceSyncInfo mutable = oldInfo.cloneMutable();
+ mutable.setTimeStamp(getTimeStamp(), true /* clear merged */);
+ newInfo = mutable;
+ }
} else {
// reset the file timestamp to the one from the entry line
setTimeStamp(timeStamp);
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/BaseSynchronizer.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/BaseSynchronizer.java
new file mode 100644
index 000000000..e436508fa
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/BaseSynchronizer.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.syncinfo;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer;
+
+/**
+ * A base sychronizer provides access to the base sync bytes for the
+ * resources in the local workspace
+ */
+public class BaseSynchronizer extends ResourceSynchronizer {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSynchronizer#getSyncBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getSyncBytes(IResource resource) throws CVSException {
+ if (resource.getType() == IResource.FILE) {
+ // For a file, return the entry line
+ byte[] bytes = EclipseSynchronizer.getInstance().getSyncBytes(resource);
+ if (bytes != null) {
+ // Use the base sync info (i.e. no deletion or addition)
+ if (ResourceSyncInfo.isDeletion(bytes)) {
+ bytes = ResourceSyncInfo.convertFromDeletion(bytes);
+ } else if (ResourceSyncInfo.isAddition(bytes)) {
+ bytes = null;
+ }
+ }
+ return bytes;
+ } else {
+ // For a folder, return the folder sync info bytes
+ FolderSyncInfo info = EclipseSynchronizer.getInstance().getFolderSync((IContainer)resource);
+ if (info == null) return null;
+ return info.getBytes();
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/OptimizedRemoteSynchronizer.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/OptimizedRemoteSynchronizer.java
new file mode 100644
index 000000000..33833ab95
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/OptimizedRemoteSynchronizer.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.syncinfo;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.util.Util;
+
+/**
+ * The optimized remote synchronizer uses the base sync info when the remote
+ * is unknown
+ */
+public class OptimizedRemoteSynchronizer extends RemoteSynchronizer {
+
+ // The local synchronizer is used for cases where the remote is unknown
+ private BaseSynchronizer baseSynchronizer = new BaseSynchronizer();
+
+ /**
+ * @param id
+ */
+ public OptimizedRemoteSynchronizer(String id) {
+ super(id, null /* use the tag in the local workspace resources */);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSynchronizer#getSyncBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getSyncBytes(IResource resource) throws CVSException {
+ byte[] bytes = getRemoteBytes(resource);
+ if (bytes == null) {
+ // The remote was never known so use the base
+ bytes = baseSynchronizer.getSyncBytes(resource);
+ }
+ return bytes;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.core.syncinfo.RemoteSynchronizer#setSyncBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public void setSyncBytes(IResource resource, byte[] bytes) throws CVSException {
+ byte[] baseBytes = baseSynchronizer.getSyncBytes(resource);
+ if (baseBytes != null && Util.equals(baseBytes, bytes)) {
+ // Remove the existing bytes so the base will be used (thus saving space)
+ removeSyncBytes(resource, IResource.DEPTH_ZERO);
+ } else {
+ super.setSyncBytes(resource, bytes);
+ }
+
+ }
+
+ /**
+ * @return
+ */
+ public BaseSynchronizer getBaseSynchronizer() {
+ return baseSynchronizer;
+ }
+
+ /**
+ * Return the bytes for the remote resource if there is a remote that differs
+ * from the local.
+ *
+ * @param resource
+ * @return
+ * @throws CVSException
+ */
+ public byte[] getRemoteBytes(IResource resource) throws CVSException {
+ return super.getSyncBytes(resource);
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteResourceFactory.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteResourceFactory.java
new file mode 100644
index 000000000..21518f888
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteResourceFactory.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.syncinfo;
+
+/**
+ * @author Administrator
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+public class RemoteResourceFactory {
+
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteSynchronizer.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteSynchronizer.java
new file mode 100644
index 000000000..3ec00d973
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/RemoteSynchronizer.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.syncinfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ISynchronizer;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSTag;
+import org.eclipse.team.internal.ccvs.core.ICVSFolder;
+import org.eclipse.team.internal.ccvs.core.ICVSRemoteResource;
+import org.eclipse.team.internal.ccvs.core.ICVSResource;
+import org.eclipse.team.internal.ccvs.core.Policy;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.core.resources.RemoteResource;
+import org.eclipse.team.internal.ccvs.core.util.Assert;
+import org.eclipse.team.internal.ccvs.core.util.Util;
+
+/**
+ * A remote resource sychronizer caches the remote sync bytes that can be
+ * used to create remote handles
+ */
+public class RemoteSynchronizer extends ResourceSynchronizer {
+
+ public static final String SYNC_KEY_QUALIFIER = "org.eclipse.team.cvs";
+
+ private static final byte[] NO_REMOTE = new byte[0];
+
+ private QualifiedName syncName;
+ private Set changedResources = new HashSet();
+ private CVSTag tag;
+
+ public RemoteSynchronizer(String id, CVSTag tag) {
+ syncName = new QualifiedName(SYNC_KEY_QUALIFIER, id);
+ getSynchronizer().add(syncName);
+ this.tag = tag;
+ }
+
+ /**
+ * Dispose of any cached remote sync info.
+ */
+ public void dispose() {
+ getSynchronizer().remove(getSyncName());
+ }
+
+ private ISynchronizer getSynchronizer() {
+ return ResourcesPlugin.getWorkspace().getSynchronizer();
+ }
+
+ private QualifiedName getSyncName() {
+ return syncName;
+ }
+
+ /*
+ * Get the sync bytes for the remote resource
+ */
+ public byte[] getSyncBytes(IResource resource) throws CVSException {
+ try {
+ return getSynchronizer().getSyncInfo(getSyncName(), resource);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ }
+ }
+
+ public void setSyncBytes(IResource resource, byte[] bytes) throws CVSException {
+ byte[] oldBytes = getSyncBytes(resource);
+ if (oldBytes != null && Util.equals(oldBytes, bytes)) return;
+ try {
+ getSynchronizer().setSyncInfo(getSyncName(), resource, bytes);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ }
+ changedResources.add(resource);
+ }
+
+ public void removeSyncBytes(IResource resource, int depth) throws CVSException {
+ if (resource.exists() || resource.isPhantom()) {
+ try {
+ getSynchronizer().flushSyncInfo(getSyncName(), resource, depth);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ }
+ changedResources.add(resource);
+ }
+ }
+
+ /**
+ * @param resource
+ */
+ public void collectChanges(IResource local, ICVSRemoteResource remote, int depth, IProgressMonitor monitor) throws TeamException {
+ byte[] remoteBytes;
+ if (remote != null) {
+ remoteBytes = ((RemoteResource)remote).getSyncBytes();
+ } else {
+ remoteBytes = NO_REMOTE;
+ }
+ setSyncBytes(local, remoteBytes);
+ if (depth == IResource.DEPTH_ZERO) return;
+ Map children = mergedMembers(local, remote, monitor);
+ for (Iterator it = children.keySet().iterator(); it.hasNext();) {
+ IResource localChild = (IResource) it.next();
+ ICVSRemoteResource remoteChild = (ICVSRemoteResource)children.get(localChild);
+ collectChanges(localChild, remoteChild,
+ depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO,
+ monitor);
+ }
+ }
+
+ protected Map mergedMembers(IResource local, IRemoteResource remote, IProgressMonitor progress) throws TeamException {
+
+ // {IResource -> IRemoteResource}
+ Map mergedResources = new HashMap();
+
+ IRemoteResource[] remoteChildren = getRemoteChildren(remote, progress);
+
+ IResource[] localChildren = getLocalChildren(local);
+
+ if (remoteChildren.length > 0 || localChildren.length > 0) {
+ List syncChildren = new ArrayList(10);
+ Set allSet = new HashSet(20);
+ Map localSet = null;
+ Map remoteSet = null;
+
+ if (localChildren.length > 0) {
+ localSet = new HashMap(10);
+ for (int i = 0; i < localChildren.length; i++) {
+ IResource localChild = localChildren[i];
+ String name = localChild.getName();
+ localSet.put(name, localChild);
+ allSet.add(name);
+ }
+ }
+
+ if (remoteChildren.length > 0) {
+ remoteSet = new HashMap(10);
+ for (int i = 0; i < remoteChildren.length; i++) {
+ IRemoteResource remoteChild = remoteChildren[i];
+ String name = remoteChild.getName();
+ remoteSet.put(name, remoteChild);
+ allSet.add(name);
+ }
+ }
+
+ Iterator e = allSet.iterator();
+ while (e.hasNext()) {
+ String keyChildName = (String) e.next();
+
+ if (progress != null) {
+ if (progress.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ // XXX show some progress?
+ }
+
+ IResource localChild =
+ localSet != null ? (IResource) localSet.get(keyChildName) : null;
+
+ IRemoteResource remoteChild =
+ remoteSet != null ? (IRemoteResource) remoteSet.get(keyChildName) : null;
+
+ if (localChild == null) {
+ // there has to be a remote resource available if we got this far
+ Assert.isTrue(remoteChild != null);
+ boolean isContainer = remoteChild.isContainer();
+ localChild = getResourceChild(local /* parent */, keyChildName, isContainer);
+ }
+ mergedResources.put(localChild, remoteChild);
+ }
+ }
+ return mergedResources;
+ }
+
+ private IRemoteResource[] getRemoteChildren(IRemoteResource remote, IProgressMonitor progress) throws TeamException {
+ return remote != null ? remote.members(progress) : new IRemoteResource[0];
+ }
+
+ private IResource[] getLocalChildren(IResource local) throws TeamException {
+ IResource[] localChildren;
+ if( local.getType() != IResource.FILE && local.exists() ) {
+ // TODO: This should be a list of all non-ignored resources including outgoing deletions
+ ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)local);
+ ICVSResource[] cvsChildren = cvsFolder.members(ICVSFolder.MANAGED_MEMBERS | ICVSFolder.UNMANAGED_MEMBERS);
+ List resourceChildren = new ArrayList();
+ for (int i = 0; i < cvsChildren.length; i++) {
+ ICVSResource cvsResource = cvsChildren[i];
+ resourceChildren.add(cvsResource.getIResource());
+ }
+ localChildren = (IResource[]) resourceChildren.toArray(new IResource[resourceChildren.size()]);
+ } else {
+ localChildren = new IResource[0];
+ }
+ return localChildren;
+ }
+
+ /*
+ * Returns a handle to a non-existing resource.
+ */
+ private IResource getResourceChild(IResource parent, String childName, boolean isContainer) {
+ if (parent.getType() == IResource.FILE) {
+ return null;
+ }
+ if (isContainer) {
+ return ((IContainer) parent).getFolder(new Path(childName));
+ } else {
+ return ((IContainer) parent).getFile(new Path(childName));
+ }
+ }
+
+ /**
+ *
+ * @param resource
+ * @return
+ * @throws TeamException
+ */
+ public IRemoteResource getRemoteResource(IResource resource) throws TeamException {
+ byte[] remoteBytes = getSyncBytes(resource);
+ if (remoteBytes == null || remoteDoesNotExist(remoteBytes)) {
+ // The remote is known to not exist or there is no base
+ return null;
+ } else {
+ return super.getRemoteResource(resource);
+ }
+ }
+
+ /*
+ * Return true if the given bytes indocate that the remote does not exist.
+ * The provided byte array must not be null;
+ */
+ private boolean remoteDoesNotExist(byte[] remoteBytes) {
+ Assert.isNotNull(remoteBytes);
+ return Util.equals(remoteBytes, NO_REMOTE);
+ }
+
+ /**
+ * @return
+ */
+ public IResource[] getChangedResources() {
+ return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+ }
+
+ public void resetChanges() {
+ changedResources.clear();
+ }
+
+ /**
+ * Refreshes the contents of the remote synchronizer and returns the list
+ * of resources whose remote sync state changed.
+ *
+ * @param resources
+ * @param depth
+ * @param monitor
+ * @return
+ * @throws TeamException
+ */
+ public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ int work = 100 * resources.length;
+ monitor.beginTask(null, work);
+ resetChanges();
+ try {
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+
+ // build the remote tree only if an initial tree hasn't been provided
+ ICVSRemoteResource tree = buildRemoteTree(resource, depth, Policy.subMonitorFor(monitor, 70));
+
+ // update the known remote handles
+ IProgressMonitor sub = Policy.infiniteSubMonitorFor(monitor, 30);
+ try {
+ sub.beginTask(null, 512);
+ removeSyncBytes(resource, IResource.DEPTH_INFINITE);
+ collectChanges(resource, tree, depth, sub);
+ } finally {
+ sub.done();
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+ IResource[] changes = getChangedResources();
+ resetChanges();
+ return changes;
+ }
+
+ /**
+ * Build a remote tree for the given parameters.
+ */
+ protected ICVSRemoteResource buildRemoteTree(IResource resource, int depth, IProgressMonitor monitor) throws TeamException {
+ // TODO: we are currently ignoring the depth parameter because the build remote tree is
+ // by default deep!
+ return CVSWorkspaceRoot.getRemoteTree(resource, tag, monitor);
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ResourceSynchronizer.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ResourceSynchronizer.java
new file mode 100644
index 000000000..e7d4f4ff4
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/syncinfo/ResourceSynchronizer.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.core.syncinfo;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.resources.RemoteFile;
+import org.eclipse.team.internal.ccvs.core.resources.RemoteFolder;
+
+/**
+ * A resource synchronizer is responsible for managing synchronization information for
+ * CVS resources.
+ */
+public abstract class ResourceSynchronizer {
+
+ public abstract byte[] getSyncBytes(IResource resource) throws CVSException;
+
+ /**
+ *
+ * @param resource
+ * @return
+ * @throws TeamException
+ */
+ public IRemoteResource getRemoteResource(IResource resource) throws TeamException {
+ byte[] remoteBytes = getSyncBytes(resource);
+ if (remoteBytes == null) {
+ // There is no remote handle for this resource
+ return null;
+ } else {
+ // TODO: This code assumes that the type of the remote resource
+ // matches that of the local resource. This may not be true.
+ // TODO: This is rather complicated. There must be a better way!
+ if (resource.getType() == IResource.FILE) {
+ return RemoteFile.fromBytes(resource, remoteBytes, getSyncBytes(resource.getParent()));
+ } else {
+ return RemoteFolder.fromBytes((IContainer)resource, remoteBytes);
+ }
+ }
+ }
+
+ /**
+ * Refreshes the contents of the resource synchronizer and returns the list
+ * of resources whose remote sync state changed.
+ * @param resources
+ * @param depth
+ * @param monitor
+ * @return
+ */
+ public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ return new IResource[0];
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSOperation.java
new file mode 100644
index 000000000..c35e3f320
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSOperation.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.operations;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSStatus;
+import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.ui.PlatformUI;
+
+
+/**
+ * This class is the abstract superclass for CVS operations. It provides
+ * error handling, prompting and other UI.
+ */
+public abstract class CVSOperation implements IRunnableWithProgress {
+
+ private boolean involvesMultipleResources = false;
+
+ private List errors = new ArrayList(); // of IStatus
+
+ protected static final IStatus OK = new CVSStatus(IStatus.OK, Policy.bind("ok")); // $NON-NLS-1$
+
+ private IRunnableContext runnableContext;
+ private Shell shell;
+ private boolean interruptable = true;
+ private boolean modifiesWorkspace = true;
+
+ // instance variable used to indicate behavior while prompting for overwrite
+ private boolean confirmOverwrite = true;
+
+ public static void run(Shell shell, CVSOperation operation) throws CVSException, InterruptedException {
+ operation.setShell(shell);
+ operation.setRunnableContext(new ProgressMonitorDialog(shell));
+ operation.execute();
+ }
+
+ /**
+ * @param shell
+ */
+ public CVSOperation(Shell shell) {
+ this.shell = shell;
+ }
+
+ /**
+ * Execute the operation in the given runnable context. If null is passed,
+ * the runnable context assigned to the operation is used.
+ *
+ * @throws InterruptedException
+ * @throws CVSException
+ */
+ public void execute(IRunnableContext aRunnableContext) throws InterruptedException, CVSException {
+ if (aRunnableContext == null) {
+ aRunnableContext = getRunnableContext();
+ }
+ try {
+ aRunnableContext.run(isInterruptable(), isInterruptable(), this);
+ } catch (InvocationTargetException e) {
+ throw CVSException.wrapException(e);
+ } catch (OperationCanceledException e) {
+ throw new InterruptedException();
+ }
+ }
+
+ public void executeWithProgress() throws CVSException, InterruptedException {
+ execute(new ProgressMonitorDialog(getShell()));
+ }
+
+ /**
+ * Execute the operation in the runnable context that has been assigned to the operation.
+ * If a context has not been assigned, the workbench window is used.
+ *
+ * @throws InterruptedException
+ * @throws CVSException
+ */
+ public void execute() throws InterruptedException, CVSException {
+ execute(getRunnableContext());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public final void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ startOperation();
+ try {
+ if (isModifiesWorkspace()) {
+ new CVSWorkspaceModifyOperation(this).execute(monitor);
+ } else {
+ execute(monitor);
+ }
+ endOperation();
+ } catch (CVSException e) {
+ // TODO: errors may not be empty
+ throw new InvocationTargetException(e);
+ } catch (CoreException e) {
+ // TODO: errors may not be empty
+ throw new InvocationTargetException(e);
+ }
+ }
+
+ protected void startOperation() {
+ resetErrors();
+ confirmOverwrite = true;
+ }
+
+ protected void endOperation() throws CVSException {
+ handleErrors((IStatus[]) errors.toArray(new IStatus[errors.size()]));
+ }
+
+ /**
+ * Subclasses must override to perform the operation
+ * @param monitor
+ * @throws CVSException
+ * @throws InterruptedException
+ */
+ public abstract void execute(IProgressMonitor monitor) throws CVSException, InterruptedException;
+
+ /**
+ * @return
+ */
+ private IRunnableContext getRunnableContext() {
+ if (runnableContext == null) {
+ return PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ }
+ return runnableContext;
+ }
+
+ /**
+ * @param context
+ */
+ public void setRunnableContext(IRunnableContext context) {
+ this.runnableContext = context;
+ }
+
+ /**
+ * @return
+ */
+ public Shell getShell() {
+ return shell;
+ }
+
+ /**
+ * @param shell
+ */
+ public void setShell(Shell shell) {
+ this.shell = shell;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isInterruptable() {
+ return interruptable;
+ }
+
+ /**
+ * @param b
+ */
+ public void setInterruptable(boolean b) {
+ interruptable = b;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isModifiesWorkspace() {
+ return modifiesWorkspace;
+ }
+
+ /**
+ * @param b
+ */
+ public void setModifiesWorkspace(boolean b) {
+ modifiesWorkspace = b;
+ }
+
+ /**
+ * @param status
+ */
+ protected void addError(IStatus status) {
+ errors.add(status);
+ }
+
+ /**
+ *
+ */
+ protected void resetErrors() {
+ errors.clear();
+ }
+
+ /**
+ * @param statuses
+ */
+ protected void handleErrors(IStatus[] status) throws CVSException {
+ if (status.length == 0) return;
+ MultiStatus result = new MultiStatus(CVSUIPlugin.ID, 0, getErrorTitle(), null);
+ for (int i = 0; i < status.length; i++) {
+ IStatus s = status[i];
+ if (s.isMultiStatus()) {
+ result.add(new CVSStatus(s.getSeverity(), s.getMessage(), s.getException()));
+ result.addAll(s);
+ } else {
+ result.add(s);
+ }
+ }
+ }
+
+ /**
+ * Provide the message used in the error status if an error occurs.
+ * Should be overriden by subclasses.
+ */
+ protected String getErrorTitle() {
+ return "Errors occured during this operation";
+ }
+
+ /**
+ * This method prompts the user to overwrite an existing resource. It uses the
+ * <code>involvesMultipleResources</code> to determine what buttons to show.
+ * @param project
+ * @return
+ */
+ protected boolean promptToOverwrite(String title, String msg) {
+ if (!confirmOverwrite) {
+ return true;
+ }
+ String buttons[];
+ if (involvesMultipleResources()) {
+ buttons = new String[] {
+ IDialogConstants.YES_LABEL,
+ IDialogConstants.YES_TO_ALL_LABEL,
+ IDialogConstants.NO_LABEL,
+ IDialogConstants.CANCEL_LABEL};
+ } else {
+ buttons = new String[] {IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL};
+ }
+ Shell displayShell = getShell();
+ final MessageDialog dialog =
+ new MessageDialog(displayShell, title, null, msg, MessageDialog.QUESTION, buttons, 0);
+
+ // run in syncExec because callback is from an operation,
+ // which is probably not running in the UI thread.
+ displayShell.getDisplay().syncExec(
+ new Runnable() {
+ public void run() {
+ dialog.open();
+ }
+ });
+ if (involvesMultipleResources()) {
+ switch (dialog.getReturnCode()) {
+ case 0://Yes
+ return true;
+ case 1://Yes to all
+ confirmOverwrite = false;
+ return true;
+ case 2://No
+ return false;
+ case 3://Cancel
+ default:
+ throw new OperationCanceledException();
+ }
+ } else {
+ return dialog.getReturnCode() == 0;
+ }
+ }
+
+ /**
+ * This method is used by <code>promptToOverwrite</code> to determine which
+ * buttons to show in the prompter.
+ *
+ * @return
+ */
+ protected boolean involvesMultipleResources() {
+ return involvesMultipleResources;
+ }
+
+ /**
+ * @param b
+ */
+ public void setInvolvesMultipleResources(boolean b) {
+ involvesMultipleResources = b;
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSWorkspaceModifyOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSWorkspaceModifyOperation.java
new file mode 100644
index 000000000..080f3244a
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/CVSWorkspaceModifyOperation.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.operations;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+/**
+ * This class wraps a CVSOperation in a workspace modify operation.
+ *
+ */
+public class CVSWorkspaceModifyOperation extends WorkspaceModifyOperation {
+
+ private CVSOperation operation;
+
+ public CVSWorkspaceModifyOperation(CVSOperation operation) {
+ super();
+ this.operation = operation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.WorkspaceModifyOperation#execute(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException {
+ try {
+ operation.execute(monitor);
+ } catch (CVSException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java
new file mode 100644
index 000000000..335423c62
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/RepositoryProviderOperation.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.operations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.RepositoryProvider;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
+import org.eclipse.team.internal.ccvs.core.CVSTeamProvider;
+import org.eclipse.team.internal.ccvs.core.ICVSResource;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+
+/**
+ * Performs a cvs operation on multiple repository providers
+ */
+public abstract class RepositoryProviderOperation extends CVSOperation {
+
+ private IResource[] resources;
+
+ /**
+ * @param shell
+ */
+ public RepositoryProviderOperation(Shell shell, IResource[] resources) {
+ super(shell);
+ this.resources = resources;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.operations.CVSOperation#execute(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void execute(IProgressMonitor monitor) throws CVSException, InterruptedException {
+ Map table = getProviderMapping(getResources());
+ Set keySet = table.keySet();
+ monitor.beginTask("", keySet.size() * 1000);
+ monitor.setTaskName(getTaskName());
+ Iterator iterator = keySet.iterator();
+ while (iterator.hasNext()) {
+ IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1000);
+ CVSTeamProvider provider = (CVSTeamProvider)iterator.next();
+ List list = (List)table.get(provider);
+ IResource[] providerResources = (IResource[])list.toArray(new IResource[list.size()]);
+ execute(provider, providerResources, subMonitor);
+ }
+
+ }
+
+ /*
+ * Helper method. Return a Map mapping provider to a list of resources
+ * shared with that provider.
+ */
+ private Map getProviderMapping(IResource[] resources) {
+ Map result = new HashMap();
+ for (int i = 0; i < resources.length; i++) {
+ RepositoryProvider provider = RepositoryProvider.getProvider(resources[i].getProject(), CVSProviderPlugin.getTypeId());
+ List list = (List)result.get(provider);
+ if (list == null) {
+ list = new ArrayList();
+ result.put(provider, list);
+ }
+ list.add(resources[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Return the resources that the operation is being performed on
+ * @return
+ */
+ protected IResource[] getResources() {
+ return resources;
+ }
+
+ /**
+ * Set the resources that the operation is to be performed on
+ * @param resources
+ */
+ protected void setResources(IResource[] resources) {
+ this.resources = resources;
+ }
+
+ /**
+ * Return the task name associated with the operation. This task name
+ * will appear in progress feedback presented to the user.
+ * @return
+ */
+ protected abstract String getTaskName();
+
+ /**
+ * Execute the operation on the resources for the given provider.
+ * @param provider
+ * @param providerResources
+ * @param subMonitor
+ * @throws CVSException
+ * @throws InterruptedException
+ */
+ protected abstract void execute(CVSTeamProvider provider, IResource[] resources, IProgressMonitor monitor) throws CVSException, InterruptedException;
+
+ protected ICVSResource[] getCVSArguments(IResource[] resources) {
+ ICVSResource[] cvsResources = new ICVSResource[resources.length];
+ for (int i = 0; i < cvsResources.length; i++) {
+ cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(resources[i]);
+ }
+ return cvsResources;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/UpdateOnlyMergableOperation.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/UpdateOnlyMergableOperation.java
new file mode 100644
index 000000000..71f3f5dd0
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/operations/UpdateOnlyMergableOperation.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.operations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSTeamProvider;
+import org.eclipse.team.internal.ccvs.core.ICVSRunnable;
+import org.eclipse.team.internal.ccvs.core.client.Command;
+import org.eclipse.team.internal.ccvs.core.client.Session;
+import org.eclipse.team.internal.ccvs.core.client.UpdateMergableOnly;
+import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+
+/**
+ * This operation performs an update that will only effect files
+ * that have no conflicts or automergable conflicts.
+ */
+public class UpdateOnlyMergableOperation extends RepositoryProviderOperation {
+
+ private LocalOption[] localOptions;
+
+ List skippedFiles = new ArrayList();
+
+ /**
+ * @param shell
+ * @param resources
+ */
+ public UpdateOnlyMergableOperation(Shell shell, IResource[] resources, LocalOption[] localOptions) {
+ super(shell, resources);
+ this.localOptions = localOptions;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#getTaskName()
+ */
+ protected String getTaskName() {
+ return "Updating";
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#execute(org.eclipse.team.internal.ccvs.core.CVSTeamProvider, org.eclipse.core.resources.IResource[], org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void execute(CVSTeamProvider provider, final IResource[] resources, IProgressMonitor monitor) throws CVSException, InterruptedException {
+ CVSWorkspaceRoot workspaceRoot = provider.getCVSWorkspaceRoot();
+ Session.run(workspaceRoot.getRemoteLocation(), workspaceRoot.getLocalRoot(), true /* output to console */,
+ new ICVSRunnable() {
+ public void run(IProgressMonitor monitor) throws CVSException {
+ UpdateMergableOnly update = new UpdateMergableOnly();
+ IStatus status = update.execute(
+ Command.NO_GLOBAL_OPTIONS,
+ localOptions,
+ getCVSArguments(resources),
+ null,
+ monitor);
+ if (status.isOK()) {
+ addSkippedFiles(update.getSkippedFiles());
+ } else {
+ addError(status);
+ }
+ }
+ }, monitor);
+
+ }
+
+ /**
+ * @param files
+ */
+ protected void addSkippedFiles(IFile[] files) {
+ skippedFiles.addAll(Arrays.asList(files));
+ }
+
+ public IFile[] getSkippedFiles() {
+ return (IFile[]) skippedFiles.toArray(new IFile[skippedFiles.size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/CVSSubscriberAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/CVSSubscriberAction.java
new file mode 100644
index 000000000..0cb8d1cb4
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/CVSSubscriberAction.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
+import org.eclipse.team.internal.ccvs.core.CVSSyncInfo;
+import org.eclipse.team.internal.ccvs.core.ICVSResource;
+import org.eclipse.team.internal.ccvs.core.ICVSRunnable;
+import org.eclipse.team.internal.ccvs.core.client.PruneFolderVisitor;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.SubscriberAction;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+import org.eclipse.ui.PlatformUI;
+
+public abstract class CVSSubscriberAction extends SubscriberAction {
+
+ protected boolean isOutOfSync(SyncResource resource) {
+ if (resource == null) return false;
+ return (!(resource.getKind() == 0) || ! resource.getLocalResource().exists());
+ }
+
+ protected void makeInSync(SyncResource[] folders) throws TeamException {
+ // If a node has a parent that is an incoming folder creation, we have to
+ // create that folder locally and set its sync info before we can get the
+ // node itself. We must do this for all incoming folder creations (recursively)
+ // in the case where there are multiple levels of incoming folder creations.
+ for (int i = 0; i < folders.length; i++) {
+ SyncResource resource = folders[i];
+ makeInSync(resource);
+ }
+ }
+
+ protected void makeInSync(SyncResource element) throws TeamException {
+ if (isOutOfSync(element)) {
+ SyncResource parent = element.getParent();
+ if (parent != null) {
+ makeInSync(parent);
+ }
+ SyncInfo info = element.getSyncInfo();
+ if (info == null) return;
+ if (info instanceof CVSSyncInfo) {
+ CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
+ cvsInfo.makeInSync();
+ }
+ }
+ }
+
+ protected void makeOutgoing(SyncResource[] folders, IProgressMonitor monitor) throws TeamException {
+ // If a node has a parent that is an incoming folder creation, we have to
+ // create that folder locally and set its sync info before we can get the
+ // node itself. We must do this for all incoming folder creations (recursively)
+ // in the case where there are multiple levels of incoming folder creations.
+ monitor.beginTask(null, 100 * folders.length);
+ for (int i = 0; i < folders.length; i++) {
+ SyncResource resource = folders[i];
+ makeOutgoing(resource, Policy.subMonitorFor(monitor, 100));
+ }
+ monitor.done();
+ }
+
+ private void makeOutgoing(SyncResource resource, IProgressMonitor monitor) throws TeamException {
+ SyncInfo info = resource.getSyncInfo();
+ if (info == null) return;
+ if (info instanceof CVSSyncInfo) {
+ CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
+ cvsInfo.makeOutgoing(monitor);
+ }
+ }
+
+ /**
+ * Handle the exception by showing an error dialog to the user.
+ * Sync actions seem to need to be sync-execed to work
+ * @param t
+ */
+ protected void handle(Throwable t) {
+ CVSUIPlugin.openError(getShell(), getErrorTitle(), null, t, CVSUIPlugin.PERFORM_SYNC_EXEC | CVSUIPlugin.LOG_NONTEAM_EXCEPTIONS);
+ }
+
+ /**
+ * Return the error title that will appear in any error dialogs shown to the user
+ * @return
+ */
+ protected String getErrorTitle() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
+ */
+ public void run(IAction action) {
+// TODO: Saving can change the sync state! How should this be handled?
+// boolean result = saveIfNecessary();
+// if (!result) return null;
+
+ SyncResourceSet syncSet = getFilteredSyncResourceSet(getFilteredSyncResources());
+ if (syncSet == null || syncSet.isEmpty()) return;
+ try {
+ getRunnableContext().run(true /* fork */, true /* cancelable */, getRunnable(syncSet));
+ } catch (InvocationTargetException e) {
+ handle(e);
+ } catch (InterruptedException e) {
+ // nothing to do;
+ }
+ }
+
+ /**
+ * Return an IRunnableWithProgress that will operate on the given sync set.
+ * This method is invoked by <code>run(IAction)</code> when the action is
+ * executed from a menu. The default implementation invokes the method
+ * <code>run(SyncResourceSet, IProgressMonitor)</code>.
+ * @param syncSet
+ * @return
+ */
+ protected IRunnableWithProgress getRunnable(final SyncResourceSet syncSet) {
+ return new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()).run(
+ new ICVSRunnable() {
+ public void run(IProgressMonitor monitor) throws CVSException {
+ CVSSubscriberAction.this.run(syncSet, monitor);
+ }
+ }, monitor);
+ } catch (CVSException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ };
+ }
+
+ protected abstract void run(SyncResourceSet syncSet, IProgressMonitor monitor) throws CVSException;
+
+ protected IRunnableContext getRunnableContext() {
+ return PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ }
+
+ /**
+ * Filter the sync resource set using action specific criteria or input from the user.
+ */
+ protected SyncResourceSet getFilteredSyncResourceSet(SyncResource[] selectedResources) {
+ // If there are conflicts or outgoing changes in the syncSet, we need to warn the user.
+ return new SyncResourceSet(selectedResources);
+ }
+
+ protected void pruneEmptyParents(SyncResource[] nodes) throws CVSException {
+ // TODO: A more explicit tie in to the pruning mechanism would be prefereable.
+ // i.e. I don't like referencing the option and visitor directly
+ if (!CVSProviderPlugin.getPlugin().getPruneEmptyDirectories()) return;
+ ICVSResource[] cvsResources = new ICVSResource[nodes.length];
+ for (int i = 0; i < cvsResources.length; i++) {
+ cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(nodes[i].getLocalResource());
+ }
+ new PruneFolderVisitor().visit(
+ CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()),
+ cvsResources);
+ }
+
+ public CVSSyncInfo getCVSSyncInfo(SyncResource syncResource) {
+ SyncInfo info = syncResource.getSyncInfo();
+ if (info instanceof CVSSyncInfo) {
+ return (CVSSyncInfo)info;
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeDialog.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeDialog.java
new file mode 100644
index 000000000..138560a1b
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeDialog.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This dialog is presented to the user when non-mergable conflicts
+ * existed in a merge update.
+ */
+public class MergeDialog extends SyncResourceSetDetailsDialog {
+
+ /**
+ * @param parentShell
+ * @param dialogTitle
+ * @param syncSet
+ */
+ public MergeDialog(Shell parentShell, SyncResourceSet syncSet) {
+ super(parentShell, "Overwrite Unmergable Resources?", syncSet);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.DetailsDialog#createMainDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ protected void createMainDialogArea(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout());
+
+ // TODO: set F1 help
+ //WorkbenchHelp.setHelp(composite, IHelpContextIds.ADD_TO_VERSION_CONTROL_DIALOG);
+
+ createWrappingLabel(composite, "All mergable resources have been updated. However, some non-mergable resources remain. Should these resources be updated, ignoring any local changes?");
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeUpdateAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeUpdateAction.java
new file mode 100644
index 000000000..297d37025
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/MergeUpdateAction.java
@@ -0,0 +1,327 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSMergeSubscriber;
+import org.eclipse.team.internal.ccvs.core.CVSSyncInfo;
+import org.eclipse.team.internal.ccvs.core.CVSTag;
+import org.eclipse.team.internal.ccvs.core.ICVSFolder;
+import org.eclipse.team.internal.ccvs.core.client.Command;
+import org.eclipse.team.internal.ccvs.core.client.Update;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ccvs.ui.operations.UpdateOnlyMergableOperation;
+import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.AndSyncInfoFilter;
+import org.eclipse.team.ui.sync.OrSyncInfoFilter;
+import org.eclipse.team.ui.sync.SyncInfoDirectionFilter;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This action performs a "cvs update -j start -j end ..." to merge changes
+ * into the local workspace.
+ */
+public class MergeUpdateAction extends SubscriberUpdateAction {
+
+ private List skippedFiles = new ArrayList();
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#run(org.eclipse.team.ui.sync.SyncResourceSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void run(SyncResourceSet syncSet, IProgressMonitor monitor) throws CVSException {
+ try {
+
+ // First, remove any known failure cases
+ SyncInfoFilter failFilter = getKnownFailureCases();
+ SyncResource[] willFail = syncSet.getNodes(failFilter);
+ syncSet.rejectNodes(failFilter);
+ skippedFiles.clear();
+
+ monitor.beginTask(null, (syncSet.size() + willFail.length) * 100);
+
+ // Run the update on the remaining nodes in the set
+ // The update will fail for conflicts that turn out to be non-automergable
+ super.run(syncSet, Policy.subMonitorFor(monitor, syncSet.size() * 100));
+
+ // It is possible that some of the conflicting changes were not auto-mergable
+ SyncResourceSet failedSet = createFailedSet(syncSet, willFail, (IFile[]) skippedFiles.toArray(new IFile[skippedFiles.size()]));
+ if (failedSet.isEmpty()) return;
+ promptForOverwrite(failedSet);
+ runOverwrite(failedSet.getSyncResources(), Policy.subMonitorFor(monitor, willFail.length * 100));
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * @param syncSet
+ * @param willFail
+ * @param files
+ * @return
+ */
+ private SyncResourceSet createFailedSet(SyncResourceSet syncSet, SyncResource[] willFail, IFile[] files) {
+ List result = new ArrayList();
+ for (int i = 0; i < files.length; i++) {
+ IFile file = files[i];
+ SyncResource resource = syncSet.getNodeFor(file);
+ if (resource != null) result.add(resource);
+ }
+ for (int i = 0; i < willFail.length; i++) {
+ result.add(willFail[i]);
+ }
+ return new SyncResourceSet((SyncResource[]) result.toArray(new SyncResource[result.size()]));
+ }
+
+ /*
+ * Return a filter which selects the cases that we know ahead of time
+ * will fail on a merge
+ */
+ private SyncInfoFilter getKnownFailureCases() {
+ return new OrSyncInfoFilter(new SyncInfoFilter[] {
+ // Conflicting additions will fail
+ SyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.ADDITION),
+ // Conflicting changes involving a deletion on one side will aways fail
+ new AndSyncInfoFilter(new SyncInfoFilter[] {
+ SyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.CHANGE),
+ new SyncInfoFilter() {
+ public boolean select(SyncInfo info) {
+ IRemoteResource remote = info.getRemote();
+ IRemoteResource base = info.getBase();
+ if (info.getLocal().exists()) {
+ // local != base and no remote will fail
+ return (base != null && remote == null);
+ } else {
+ // no local and base != remote
+ return (base != null && remote != null && !base.equals(remote));
+ }
+ }
+ }
+ })
+ });
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SubscriberAction#getSyncInfoFilter()
+ */
+ protected SyncInfoFilter getSyncInfoFilter() {
+ // Update works for all incoming and conflicting nodes
+ // TODO: there should be an instance variable for the filter
+ return new OrSyncInfoFilter(new SyncInfoFilter[] {
+ new SyncInfoDirectionFilter(SyncInfo.INCOMING),
+ new SyncInfoDirectionFilter(SyncInfo.CONFLICTING)
+ });
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberUpdateAction#runUpdateDeletions(org.eclipse.team.internal.ui.sync.views.SyncResource[], org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void runUpdateDeletions(SyncResource[] nodes, RepositoryManager manager, IProgressMonitor monitor) throws TeamException {
+ // When merging, update deletions become outgoing deletions so just delete
+ // the files locally without unmanaging (so the sync info is kept to
+ // indicate an outgoing deletion
+ try {
+ monitor.beginTask(null, 100 * nodes.length);
+ for (int i = 0; i < nodes.length; i++) {
+ IResource resource = nodes[i].getResource();
+ if (resource.getType() == IResource.FILE) {
+ ((IFile)resource).delete(false /* force */, true /* keep local history */, Policy.subMonitorFor(monitor, 100));
+ }
+ }
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberUpdateAction#runUpdateShallow(org.eclipse.team.internal.ui.sync.views.SyncResource[], org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void runUpdateShallow(SyncResource[] nodes, RepositoryManager manager, IProgressMonitor monitor) throws TeamException {
+ // Incoming additions require different handling then incoming changes and deletions
+ List additions = new ArrayList();
+ List changes = new ArrayList();
+ for (int i = 0; i < nodes.length; i++) {
+ SyncResource resource = nodes[i];
+ int kind = resource.getKind();
+ if ((kind & SyncInfo.ADDITION) != 0) {
+ additions.add(resource);
+ } else {
+ changes.add(resource);
+ }
+ }
+ if (!additions.isEmpty()) {
+ mergeWithLocal((SyncResource[]) additions.toArray(new SyncResource[additions.size()]), manager, false /* include start tag */, monitor);
+ }
+ if (!changes.isEmpty()) {
+ mergeWithLocal((SyncResource[]) changes.toArray(new SyncResource[changes.size()]), manager, true /* include start tag */, monitor);
+ }
+ }
+
+ /*
+ * Use "cvs update -j start -j end ..." to merge changes. This method will result in
+ * an error for addition conflicts.
+ */
+ protected void mergeWithLocal(SyncResource[] nodes, RepositoryManager manager, boolean includeStartTag, IProgressMonitor monitor) throws TeamException {
+ TeamSubscriber subscriber = getSubscriber();
+ if (!(subscriber instanceof CVSMergeSubscriber)) {
+ throw new CVSException("Invalid subscriber: " + subscriber.getId());
+ }
+ CVSTag startTag = ((CVSMergeSubscriber)subscriber).getStartTag();
+ CVSTag endTag = ((CVSMergeSubscriber)subscriber).getEndTag();
+
+ Command.LocalOption[] options;
+ if (includeStartTag) {
+ options = new Command.LocalOption[] {
+ Command.DO_NOT_RECURSE,
+ Update.makeArgumentOption(Update.JOIN, startTag.getName()),
+ Update.makeArgumentOption(Update.JOIN, endTag.getName()) };
+ } else {
+ options = new Command.LocalOption[] {
+ Command.DO_NOT_RECURSE,
+ Update.makeArgumentOption(Update.JOIN, endTag.getName()) };
+ }
+
+ // run a join update using the start and end tags and the join points
+ try {
+ UpdateOnlyMergableOperation operation = new UpdateOnlyMergableOperation(getShell(), getIResourcesFrom(nodes), options);
+ operation.run(monitor);
+ addSkippedFiles(operation.getSkippedFiles());
+ } catch (InvocationTargetException e) {
+ throw CVSException.wrapException(e);
+ } catch (InterruptedException e) {
+ Policy.cancelOperation();
+ }
+ }
+
+ private void addSkippedFiles(IFile[] files) {
+ skippedFiles.addAll(Arrays.asList(files));
+ }
+
+ /* (non-Javadoc)
+ *
+ * Return true for all conflicting changes since the server does not report
+ * automergable conflicts properly for merge updates.
+ *
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberUpdateAction#supportsShallowUpdateFor(org.eclipse.team.internal.ui.sync.views.SyncResource)
+ */
+ protected boolean supportsShallowUpdateFor(SyncResource changedNode) {
+ return (changedNode.getChangeDirection() == SyncInfo.CONFLICTING
+ && ((changedNode.getKind() & SyncInfo.CHANGE_MASK) == SyncInfo.CHANGE));
+ }
+
+ /**
+ * Prompt for mergeable conflicts.
+ * Note: This method is designed to be overridden by test cases.
+ *
+ * @return 0 to cancel, 1 to only update mergeable conflicts, 2 to overwrite if unmergeable
+ */
+ protected boolean promptForOverwrite(final SyncResourceSet syncSet) {
+ final int[] result = new int[] {Dialog.CANCEL};
+ final Shell shell = getShell();
+ shell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ MergeDialog dialog = new MergeDialog(shell, syncSet);
+ result[0] = dialog.open();
+ }
+ });
+ return (result[0] == Dialog.OK);
+ }
+
+ /*
+ * @see UpdateSyncAction#runUpdateDeep(IProgressMonitor, List, RepositoryManager)
+ * incoming-change
+ * incoming-deletion
+ */
+ protected void runOverwrite(SyncResource[] nodes, IProgressMonitor monitor) throws CVSException {
+ RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
+ monitor.beginTask(null, 1000 * nodes.length);
+ try {
+ for (int i = 0; i < nodes.length; i++) {
+ makeRemoteLocal(nodes[i], Policy.subMonitorFor(monitor, 1000));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /*
+ * If called on a new folder, the folder will become an outgoing addition.
+ */
+ private void makeRemoteLocal(SyncResource element, IProgressMonitor monitor) throws CVSException {
+ SyncInfo info = element.getSyncInfo();
+ IRemoteResource remote = info.getRemote();
+ IResource local = info.getLocal();
+ try {
+ if(remote==null) {
+ local.delete(false, monitor);
+ } else {
+ if(remote.isContainer()) {
+ ensureContainerExists(element);
+ } else {
+ monitor.beginTask(null, 200);
+ try {
+ IFile localFile = (IFile)local;
+ if(local.exists()) {
+ localFile.setContents(remote.getContents(Policy.subMonitorFor(monitor, 100)), false /*don't force*/, true /*keep history*/, Policy.subMonitorFor(monitor, 100));
+ } else {
+ ensureContainerExists(element.getParent());
+ localFile.create(remote.getContents(Policy.subMonitorFor(monitor, 100)), false /*don't force*/, Policy.subMonitorFor(monitor, 100));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+ }
+ } catch(CoreException e) {
+ throw new CVSException(Policy.bind("UpdateMergeActionProblems_merging_remote_resources_into_workspace_1"), e); //$NON-NLS-1$
+ }
+ }
+
+ private void ensureContainerExists(SyncResource resource) throws CVSException {
+ IResource local = resource.getResource();
+ // make sure that the parent exists
+ if (!local.exists()) {
+ ensureContainerExists(resource.getParent());
+ }
+ // make sure that the folder sync info is set
+ SyncInfo info = resource.getSyncInfo();
+ if (info instanceof CVSSyncInfo) {
+ CVSSyncInfo cvsInfo = (CVSSyncInfo)info;
+ cvsInfo.makeInSync();
+ }
+ // create the folder if it doesn't exist
+ ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)local);
+ if (!cvsFolder.exists()) {
+ cvsFolder.mkdir();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberCommitAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberCommitAction.java
new file mode 100644
index 000000000..b88748f80
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberCommitAction.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.ICVSFile;
+import org.eclipse.team.internal.ccvs.core.ICVSFolder;
+import org.eclipse.team.internal.ccvs.core.ICVSResource;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+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.repo.RepositoryManager;
+import org.eclipse.team.internal.ccvs.ui.sync.ToolTipMessageDialog;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * @author Administrator
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+public class SubscriberCommitAction extends CVSSubscriberAction {
+
+ private String comment;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#getFilteredSyncResourceSet(org.eclipse.team.internal.ui.sync.views.SyncResource[])
+ */
+ protected SyncResourceSet getFilteredSyncResourceSet(SyncResource[] selectedResources) {
+ SyncResourceSet syncSet = super.getFilteredSyncResourceSet(selectedResources);
+ if (!promptForConflictHandling(syncSet)) return null;
+ try {
+ if (!promptForUnaddedHandling(syncSet)) return null;
+ } catch (CVSException e) {
+ // TODO Could prompt the user with option to continue
+ // instead of just continuing
+ CVSUIPlugin.log(e);
+ }
+ return syncSet;
+ }
+
+ /**
+ * @param syncSet
+ * @return
+ */
+ private boolean promptForConflictHandling(SyncResourceSet syncSet) {
+ // If there is a conflict in the syncSet, we need to prompt the user before proceeding.
+ if (syncSet.hasConflicts() || syncSet.hasIncomingChanges()) {
+ switch (promptForConflicts(syncSet)) {
+ case 0:
+ // Yes, synchronize conflicts as well
+ break;
+ case 1:
+ // No, only synchronize non-conflicting changes.
+ syncSet.removeConflictingNodes();
+ syncSet.removeIncomingNodes();
+ break;
+ case 2:
+ default:
+ // Cancel
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean promptForUnaddedHandling(SyncResourceSet syncSet) throws CVSException {
+ if (syncSet.isEmpty()) return false;
+
+ // accumulate any resources that are not under version control
+ IResource[] unadded = getUnaddedResources(syncSet);
+
+ // prompt to get comment and any resources to be added to version control
+ RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
+ IResource[] toBeAdded = promptForResourcesToBeAdded(manager, unadded);
+ if (toBeAdded == null) return false; // User cancelled.
+ comment = promptForComment(manager, syncSet.getResources());
+ if (comment == null) return false; // User cancelled.
+
+ // remove unshared resources that were not selected by the user
+ if (unadded != null && unadded.length > 0) {
+ List resourcesToRemove = new ArrayList(unadded.length);
+ for (int i = 0; i < unadded.length; i++) {
+ IResource unaddedResource = unadded[i];
+ boolean included = false;
+ for (int j = 0; j < toBeAdded.length; j++) {
+ IResource resourceToAdd = toBeAdded[j];
+ if (unaddedResource.equals(resourceToAdd)) {
+ included = true;
+ break;
+ }
+ }
+ if (!included)
+ resourcesToRemove.add(unaddedResource);
+ }
+ syncSet.removeResources((IResource[]) resourcesToRemove.toArray(new IResource[resourcesToRemove.size()]));
+ }
+ return true;
+ }
+
+ /**
+ * @param syncSet
+ * @return
+ */
+ private IResource[] getUnaddedResources(SyncResourceSet syncSet) throws CVSException {
+ // TODO: should only get outgoing additions (since conflicting additions
+ // could be considered to be under version control already)
+ IResource[] resources = syncSet.getResources();
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (!isAdded(resource)) {
+ result.add(resource);
+ }
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ private boolean isAdded(IResource resource) throws CVSException {
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ if (cvsResource.isFolder()) {
+ return ((ICVSFolder)cvsResource).isCVSFolder();
+ } else {
+ return cvsResource.isManaged();
+ }
+ }
+
+ private boolean isRemoved(IResource resource) throws CVSException {
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ if (!cvsResource.isFolder()) {
+ byte[] syncBytes = ((ICVSFile)cvsResource).getSyncBytes();
+ if (syncBytes == null) return true;
+ return ResourceSyncInfo.isDeletion(syncBytes);
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#run(org.eclipse.team.ui.sync.SyncResourceSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void run(SyncResourceSet syncSet, IProgressMonitor monitor) throws CVSException {
+
+ final SyncResource[] changed = syncSet.getSyncResources();
+ if (changed.length == 0) return;
+
+ // A list of files to be committed
+ final List commits = new ArrayList(); // of IResource
+ // New resources that are not yet under CVS control and need a "cvs add"
+ final List additions = new ArrayList(); // of IResource
+ // Deleted resources that need a "cvs remove"
+ final List deletions = new ArrayList(); // of IResource
+ // A list of incoming or conflicting file changes to be made outgoing changes
+ final List makeOutgoing = new ArrayList(); // of SyncResource
+ // A list of out-of-sync folders that must be made in-sync
+ final List makeInSync = new ArrayList(); // of SyncResource
+
+ for (int i = 0; i < changed.length; i++) {
+ SyncResource changedNode = changed[i];
+ int kind = changedNode.getKind();
+ IResource resource = changedNode.getResource();
+
+ // Any parent folders should be made in-sync.
+ // Steps will be taken after the commit to prune any empty folders
+ SyncResource parent = changedNode.getParent();
+ if (parent != null) {
+ if (isOutOfSync(parent)) {
+ makeInSync.add(parent);
+ }
+ }
+
+ if (resource.getType() == IResource.FILE) {
+ // By default, all files are committed
+ commits.add(resource);
+ // Determine what other work needs to be done for the file
+ switch (kind & SyncInfo.DIRECTION_MASK) {
+ case SyncInfo.INCOMING:
+ // Convert the incoming change to an outgoing change
+ makeOutgoing.add(changedNode);
+ break;
+ case SyncInfo.OUTGOING:
+ switch (kind & SyncInfo.CHANGE_MASK) {
+ case SyncInfo.ADDITION:
+ // Outgoing addition. 'add' it before committing.
+ if (!isAdded(resource))
+ additions.add(resource);
+ break;
+ case SyncInfo.DELETION:
+ // Outgoing deletion. 'delete' it before committing.
+ if (!isRemoved(resource))
+ deletions.add(resource);
+ break;
+ case SyncInfo.CHANGE:
+ // Outgoing change. Just commit it.
+ break;
+ }
+ break;
+ case SyncInfo.CONFLICTING:
+ // TODO: what about conflicting deletions
+ // Convert the conflicting change to an outgoing change
+ makeOutgoing.add(changedNode);
+ break;
+ }
+ } else {
+ if (((kind & SyncInfo.DIRECTION_MASK) == SyncInfo.OUTGOING)
+ && ((kind & SyncInfo.CHANGE_MASK) == SyncInfo.ADDITION)) {
+ // Outgoing folder additions must be added
+ additions.add(changedNode.getResource());
+ } else if (isOutOfSync(changedNode)) {
+ // otherwise, make any out-of-sync folders in-sync using the remote info
+ makeInSync.add(changedNode);
+ }
+ }
+ }
+ try {
+ // Calculate the total amount of work needed
+ int work = (makeOutgoing.size() + additions.size() + deletions.size() + commits.size()) * 100;
+ monitor.beginTask(null, work);
+
+ if (makeInSync.size() > 0) {
+ makeInSync((SyncResource[]) makeInSync.toArray(new SyncResource[makeInSync.size()]));
+ }
+
+ if (makeOutgoing.size() > 0) {
+ makeOutgoing((SyncResource[]) makeOutgoing.toArray(new SyncResource[makeInSync.size()]), Policy.subMonitorFor(monitor, makeOutgoing.size() * 100));
+ }
+
+ // TODO: There was special handling for undoing incoming deletions
+ // This should be handled by makeOutgoing but we'll need to verify
+
+ RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
+ if (additions.size() != 0) {
+ manager.add((IResource[])additions.toArray(new IResource[0]), Policy.subMonitorFor(monitor, additions.size() * 100));
+ }
+ if (deletions.size() != 0) {
+ manager.delete((IResource[])deletions.toArray(new IResource[0]), Policy.subMonitorFor(monitor, deletions.size() * 100));
+ }
+ manager.commit((IResource[])commits.toArray(new IResource[commits.size()]), comment, Policy.subMonitorFor(monitor, commits.size() * 100));
+
+ // TODO: are there any cases that need to have folders pruned?
+ } catch (TeamException e) {
+ throw CVSException.wrapException(e);
+ }
+ }
+
+ /**
+ * Prompts the user to determine how conflicting changes should be handled.
+ * Note: This method is designed to be overridden by test cases.
+ * @return 0 to sync conflicts, 1 to sync all non-conflicts, 2 to cancel
+ */
+ protected int promptForConflicts(SyncResourceSet syncSet) {
+ String[] buttons = new String[] {IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL};
+ String question = Policy.bind("CommitSyncAction.questionRelease"); //$NON-NLS-1$
+ String title = Policy.bind("CommitSyncAction.titleRelease"); //$NON-NLS-1$
+ String[] tips = new String[] {
+ Policy.bind("CommitSyncAction.releaseAll"), //$NON-NLS-1$
+ Policy.bind("CommitSyncAction.releasePart"), //$NON-NLS-1$
+ Policy.bind("CommitSyncAction.cancelRelease") //$NON-NLS-1$
+ };
+ Shell shell = getShell();
+ final ToolTipMessageDialog dialog = new ToolTipMessageDialog(shell, title, null, question, MessageDialog.QUESTION, buttons, tips, 0);
+ shell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ dialog.open();
+ }
+ });
+ return dialog.getReturnCode();
+ }
+
+ /**
+ * Prompts the user for a release comment.
+ * Note: This method is designed to be overridden by test cases.
+ * @return the comment, or null to cancel
+ */
+ protected String promptForComment(RepositoryManager manager, IResource[] resourcesToCommit) {
+ return manager.promptForComment(getShell(), resourcesToCommit);
+ }
+
+ protected IResource[] promptForResourcesToBeAdded(RepositoryManager manager, IResource[] unadded) {
+ return manager.promptForResourcesToBeAdded(getShell(), unadded);
+ }
+
+ protected String getErrorTitle() {
+ return Policy.bind("CommitAction.commitFailed"); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberConfirmMergedAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberConfirmMergedAction.java
new file mode 100644
index 000000000..33ebbd03c
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberConfirmMergedAction.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSSyncInfo;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.SyncInfoDirectionFilter;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This action marks the local resource as merged by updating the base
+ * resource revision to match the remote resource revision
+ */
+public class SubscriberConfirmMergedAction extends CVSSubscriberAction {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SubscriberAction#getSyncInfoFilter()
+ */
+ protected SyncInfoFilter getSyncInfoFilter() {
+ return new SyncInfoDirectionFilter(new int[] {SyncInfo.CONFLICTING});
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#run(org.eclipse.team.ui.sync.SyncResourceSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void run(SyncResourceSet syncSet, IProgressMonitor monitor) throws CVSException {
+ SyncResource[] syncResources = syncSet.getSyncResources();
+ List needsMerge = new ArrayList();
+ monitor.beginTask(null, 100 * syncResources.length);
+ try {
+ for (int i = 0; i < syncResources.length; i++) {
+ SyncResource resource = syncResources[i];
+
+ CVSSyncInfo cvsInfo = getCVSSyncInfo(resource);
+ if (cvsInfo != null) {
+ cvsInfo.makeOutgoing(Policy.subMonitorFor(monitor, 100));
+ }
+
+ }
+ } catch (TeamException e) {
+ handle(e);
+ } finally {
+ monitor.done();
+ }
+ }
+
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberUpdateAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberUpdateAction.java
new file mode 100644
index 000000000..541200378
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SubscriberUpdateAction.java
@@ -0,0 +1,279 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.client.Command;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This action performs an update for any CVSSyncTreeSubscriber.
+ * Warning: This action will operate on any out-of-sync resource.
+ * For non-automergable conflicts and outgoing changes, this action
+ * will repace the local resource with the remote resources (deleting
+ * the local if there is no remote). It is up to the subclass to
+ * ensure that only suitable nodes are in the sync set.
+ */
+public abstract class SubscriberUpdateAction extends CVSSubscriberAction {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#getFilteredSyncResourceSet(org.eclipse.team.internal.ui.sync.views.SyncResource[])
+ */
+ protected SyncResourceSet getFilteredSyncResourceSet(SyncResource[] selectedResources) {
+ SyncResourceSet syncSet = super.getFilteredSyncResourceSet(selectedResources);
+ if (!performPrompting(syncSet)) return null;
+ return syncSet;
+ }
+
+ /**
+ * Perform appropriate prompting given the elements in the sync set.
+ * Elements can be removed from the sync set based on user input.
+ * Returning false will cancel the operation. By default, no
+ * prompting is performed and true is returned.
+ *
+ * @param syncSet
+ * @return
+ */
+ protected boolean performPrompting(SyncResourceSet syncSet) {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#run(org.eclipse.team.ui.sync.SyncResourceSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void run(SyncResourceSet syncSet, IProgressMonitor monitor) throws CVSException {
+
+ SyncResource[] changed = syncSet.getSyncResources();
+ if (changed.length == 0) return;
+
+ // The list of sync resources to be updated using "cvs update"
+ List updateShallow = new ArrayList();
+ // A list of sync resource folders which need to be created locally
+ // (incoming addition or previously pruned)
+ Set parentCreationElements = new HashSet();
+ // A list of sync resources that are incoming deletions.
+ // We do these first to avoid case conflicts
+ List updateDeletions = new ArrayList();
+ // A list of sync resources that need to be unmanaged and locally deleted
+ // Note: This list is also used to unmanaged outgoing deletions
+ // and to remove conflict local changes when override conflicts is chosen
+ List deletions = new ArrayList();
+
+ for (int i = 0; i < changed.length; i++) {
+ SyncResource changedNode = changed[i];
+
+ // Make sure that parent folders exist
+ SyncResource parent = changedNode.getParent();
+ if (parent != null && isOutOfSync(parent)) {
+ // We need to ensure that parents that are either incoming folder additions
+ // or previously pruned folders are recreated.
+ parentCreationElements.add(parent);
+ }
+
+ IResource resource = changedNode.getResource();
+ int kind = changedNode.getKind();
+ if (resource.getType() == IResource.FILE) {
+ // add the file to the list of files to be updated
+ updateShallow.add(changedNode);
+
+ // Not all change types will require a "cvs update"
+ // Some can be deleted locally without performing an update
+ switch (kind & SyncInfo.DIRECTION_MASK) {
+ case SyncInfo.INCOMING:
+ switch (kind & SyncInfo.CHANGE_MASK) {
+ case SyncInfo.DELETION:
+ // Incoming deletions can just be deleted instead of updated
+ updateDeletions.add(changedNode);
+ updateShallow.remove(changedNode);
+ break;
+ }
+ break;
+ case SyncInfo.OUTGOING:
+ // outgoing changes can be deleted before the update
+ deletions.add(changedNode);
+ switch (kind & SyncInfo.CHANGE_MASK) {
+ case SyncInfo.ADDITION:
+ // an outgoing addition does not need an update
+ updateShallow.remove(changedNode);
+ break;
+ }
+ break;
+ case SyncInfo.CONFLICTING:
+ // conflicts can be deleted before the update
+ deletions.add(changedNode);
+ switch (kind & SyncInfo.CHANGE_MASK) {
+ case SyncInfo.DELETION:
+ // conflicting deletions do not need an update
+ updateShallow.remove(changedNode);
+ break;
+ case SyncInfo.CHANGE:
+ // some conflicting changes can be handled by an update
+ // (e.g. automergable)
+ if (supportsShallowUpdateFor(changedNode)) {
+ // Don't delete the local resource since the
+ // action can accomodate the shallow update
+ deletions.remove(changedNode);
+ }
+ break;
+ }
+ break;
+ }
+ } else {
+ // Special handling for folders to support shallow operations on files
+ // (i.e. folder operations are performed using the sync info already
+ // contained in the sync info.
+ if (isOutOfSync(changedNode)) {
+ parentCreationElements.add(changedNode);
+ } else if (((kind & SyncInfo.DIRECTION_MASK) == SyncInfo.OUTGOING)
+ && ((kind & SyncInfo.CHANGE_MASK) == SyncInfo.ADDITION)) {
+ // The folder is an outgoing addition which is being overridden
+ // Add it to the list of resources to be deleted
+ deletions.add(changedNode);
+ }
+ }
+
+ }
+ try {
+ // Calculate the total amount of work needed
+ int work = (deletions.size() + updateDeletions.size() + updateShallow.size()) * 100;
+ monitor.beginTask(null, work);
+
+ RepositoryManager manager = CVSUIPlugin.getPlugin().getRepositoryManager();
+
+ // TODO: non of the work should be done until after the connection to
+ // the repository is made
+ // TODO: deleted files that are also being updated should be written to
+ // a backup file in case the update fails. The backups could be purged after
+ // the update succeeds.
+ if (parentCreationElements.size() > 0) {
+ makeInSync((SyncResource[]) parentCreationElements.toArray(new SyncResource[parentCreationElements.size()]));
+ }
+ if (deletions.size() > 0) {
+ runLocalDeletions((SyncResource[])deletions.toArray(new SyncResource[deletions.size()]), manager, Policy.subMonitorFor(monitor, deletions.size() * 100));
+ }
+ if (updateDeletions.size() > 0) {
+ runUpdateDeletions((SyncResource[])updateDeletions.toArray(new SyncResource[updateDeletions.size()]), manager, Policy.subMonitorFor(monitor, updateDeletions.size() * 100));
+ }
+ if (updateShallow.size() > 0) {
+ runUpdateShallow((SyncResource[])updateShallow.toArray(new SyncResource[updateShallow.size()]), manager, Policy.subMonitorFor(monitor, updateShallow.size() * 100));
+ }
+ } catch (final TeamException e) {
+ throw CVSException.wrapException(e);
+ } finally {
+ monitor.done();
+ }
+ return;
+ }
+
+ /**
+ * Method which indicates whether a shallow update will work for the given
+ * node which is a conflicting change. The default is to return false.
+ *
+ * @param changedNode
+ * @return
+ */
+ protected boolean supportsShallowUpdateFor(SyncResource changedNode) {
+ return false;
+ }
+
+ /**
+ * @param element
+ */
+ protected void unmanage(SyncResource element, IProgressMonitor monitor) throws CVSException {
+ CVSWorkspaceRoot.getCVSResourceFor(element.getResource()).unmanage(monitor);
+
+ }
+
+ /**
+ * Method deleteAndKeepHistory.
+ * @param iResource
+ * @param iProgressMonitor
+ */
+ protected void deleteAndKeepHistory(IResource resource, IProgressMonitor monitor) throws CVSException {
+ try {
+ if (!resource.exists()) return;
+ if (resource.getType() == IResource.FILE)
+ ((IFile)resource).delete(false /* force */, true /* keep history */, monitor);
+ else if (resource.getType() == IResource.FOLDER)
+ ((IFolder)resource).delete(false /* force */, true /* keep history */, monitor);
+ } catch (CoreException e) {
+ throw CVSException.wrapException(e);
+ }
+ }
+
+ protected void runLocalDeletions(SyncResource[] nodes, RepositoryManager manager, IProgressMonitor monitor) throws TeamException {
+ monitor.beginTask(null, nodes.length * 100);
+ for (int i = 0; i < nodes.length; i++) {
+ SyncResource node = nodes[i];
+ unmanage(node, Policy.subMonitorFor(monitor, 50));
+ deleteAndKeepHistory(node.getResource(), Policy.subMonitorFor(monitor, 50));
+ }
+ pruneEmptyParents(nodes);
+ monitor.done();
+ }
+
+ protected void runUpdateDeletions(SyncResource[] nodes, RepositoryManager manager, IProgressMonitor monitor) throws TeamException {
+ // As an optimization, perform the deletions locally
+ runLocalDeletions(nodes, manager, monitor);
+ }
+
+ protected void runUpdateShallow(SyncResource[] nodes, RepositoryManager manager, IProgressMonitor monitor) throws TeamException {
+ // TODO: Should use custom update which skips non-automergable conflicts
+ manager.update(getIResourcesFrom(nodes), new Command.LocalOption[] { Command.DO_NOT_RECURSE }, false, monitor);
+ }
+
+ protected IResource[] getIResourcesFrom(SyncResource[] nodes) {
+ List resources = new ArrayList(nodes.length);
+ for (int i = 0; i < nodes.length; i++) {
+ resources.add(nodes[i].getResource());
+ }
+ return (IResource[]) resources.toArray(new IResource[resources.size()]);
+ }
+
+ /**
+ * Prompt for non-automergeable conflicts.
+ * Note: This method is designed to be overridden by test cases.
+ * @return false to cancel, true to overwrite local changes
+ */
+ protected boolean promptForConflicts() {
+ final boolean[] result = new boolean[] { false };
+ final Shell shell = getShell();
+ shell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ result[0] = MessageDialog.openQuestion(shell, Policy.bind("UpdateSyncAction.Overwrite_local_changes__5"), Policy.bind("UpdateSyncAction.You_have_local_changes_you_are_about_to_overwrite._Do_you_wish_to_continue__6")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ });
+ return result[0];
+ }
+
+ protected String getErrorTitle() {
+ return Policy.bind("UpdateAction.update"); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SyncResourceSetDetailsDialog.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SyncResourceSetDetailsDialog.java
new file mode 100644
index 000000000..57784a556
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/SyncResourceSetDetailsDialog.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.internal.ccvs.ui.AdaptableResourceList;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ui.DetailsDialog;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+
+/**
+ * DetailsDialog that has a details area which shows the SyncResources
+ * in a SyncResourceSet.
+ */
+public abstract class SyncResourceSetDetailsDialog extends DetailsDialog {
+
+ private static final int WIDTH_HINT = 350;
+ private final static int SELECTION_HEIGHT_HINT = 100;
+
+ private CheckboxTableViewer listViewer;
+
+ private SyncResourceSet syncSet;
+ private Object[] selectedResources;
+
+ /**
+ * @param parentShell
+ * @param dialogTitle
+ */
+ public SyncResourceSetDetailsDialog(Shell parentShell, String dialogTitle, SyncResourceSet syncSet) {
+ super(parentShell, dialogTitle);
+ this.syncSet = syncSet;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.DetailsDialog#createDropDownDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ protected Composite createDropDownDialogArea(Composite parent) {
+ Composite composite = createComposite(parent);
+
+ addResourcesArea(composite);
+
+ // TODO: set F1 help
+ //WorkbenchHelp.setHelp(composite, IHelpContextIds.ADD_TO_VERSION_CONTROL_DIALOG);
+
+ return composite;
+ }
+
+ /**
+ * @param composite
+ */
+ private void addResourcesArea(Composite composite) {
+ createWrappingLabel(composite, "The following resources will be effected by the update");
+ // add the selectable checkbox list
+
+ listViewer = CheckboxTableViewer.newCheckList(composite, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_BOTH);
+ data.heightHint = SELECTION_HEIGHT_HINT;
+ data.widthHint = WIDTH_HINT;
+ listViewer.getTable().setLayoutData(data);
+
+ // set the contents of the list
+ listViewer.setLabelProvider(new WorkbenchLabelProvider() {
+ protected String decorateText(String input, Object element) {
+ if (element instanceof IResource)
+ return ((IResource)element).getFullPath().toString();
+ else
+ return input;
+ }
+ });
+ listViewer.setContentProvider(new WorkbenchContentProvider());
+ setViewerInput();
+ listViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ selectedResources = listViewer.getCheckedElements();
+ }
+ });
+
+ addSelectionButtons(composite);
+
+ }
+
+ /**
+ * Add the selection and deselection buttons to the dialog.
+ * @param composite org.eclipse.swt.widgets.Composite
+ */
+ private void addSelectionButtons(Composite composite) {
+
+ Composite buttonComposite = new Composite(composite, SWT.RIGHT);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ buttonComposite.setLayout(layout);
+ GridData data =
+ new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.GRAB_HORIZONTAL);
+ data.grabExcessHorizontalSpace = true;
+ composite.setData(data);
+
+ Button selectButton = createButton(buttonComposite, IDialogConstants.SELECT_ALL_ID, Policy.bind("ReleaseCommentDialog.selectAll"), false); //$NON-NLS-1$
+ SelectionListener listener = new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ listViewer.setAllChecked(true);
+ selectedResources = null;
+ }
+ };
+ selectButton.addSelectionListener(listener);
+
+ Button deselectButton = createButton(buttonComposite, IDialogConstants.DESELECT_ALL_ID, Policy.bind("ReleaseCommentDialog.deselectAll"), false); //$NON-NLS-1$
+ listener = new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ listViewer.setAllChecked(false);
+ selectedResources = new Object[0];
+
+ }
+ };
+ deselectButton.addSelectionListener(listener);
+ }
+
+ protected void setViewerInput() {
+ if (listViewer == null || listViewer.getControl().isDisposed()) return;
+ selectedResources = null;
+ listViewer.setInput(new AdaptableResourceList(getAllResources()));
+ // TODO: for now, just reset the selection when the input is reset
+ if (selectedResources == null) {
+ listViewer.setAllChecked(true);
+ } else {
+ listViewer.setCheckedElements(selectedResources);
+ }
+ }
+
+ /**
+ * Return a list of all the resources that are currently under consideration by the dialog
+ */
+ protected IResource[] getAllResources() {
+ return syncSet.getResources();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.DetailsDialog#updateEnablements()
+ */
+ protected void updateEnablements() {
+ }
+
+ /**
+ * @return
+ */
+ public SyncResourceSet getSyncSet() {
+ return syncSet;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/UpdateDialog.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/UpdateDialog.java
new file mode 100644
index 000000000..8d4e4012c
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/UpdateDialog.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ccvs.ui.Policy;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.AndSyncInfoFilter;
+import org.eclipse.team.ui.sync.AutomergableFilter;
+import org.eclipse.team.ui.sync.OrSyncInfoFilter;
+import org.eclipse.team.ui.sync.SyncInfoChangeTypeFilter;
+import org.eclipse.team.ui.sync.SyncInfoDirectionFilter;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This dialog prompts for the type of update which should take place
+ * (i.e. update of auto-mergable files or update of all ignore local
+ * changes.
+ */
+public class UpdateDialog extends SyncResourceSetDetailsDialog {
+
+ private Button radio1;
+ private Button radio2;
+
+ private boolean autoMerge;
+ private SyncInfoFilter automergableFilter;
+
+ /**
+ * @param parentShell
+ * @param dialogTitle
+ * @param dialogTitleImage
+ * @param dialogMessage
+ * @param dialogImageType
+ * @param dialogButtonLabels
+ * @param defaultIndex
+ */
+ public UpdateDialog(Shell parentShell, SyncResourceSet syncSet) {
+ super(parentShell, "Overwrite Local Changes?", syncSet);
+ this.autoMerge = hasAutomergableResources();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.DetailsDialog#createMainDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ protected void createMainDialogArea(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout());
+
+ // TODO: set F1 help
+ //WorkbenchHelp.setHelp(composite, IHelpContextIds.ADD_TO_VERSION_CONTROL_DIALOG);
+
+ createWrappingLabel(composite, Policy.bind("UpdateSyncAction.You_have_local_changes_you_are_about_to_overwrite_2"));
+
+ if (hasAutomergableResources()) {
+
+ SelectionListener selectionListener = new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ Button button = (Button)e.widget;
+ if (button.getSelection()) {
+ setAutomerge(button == radio1);
+ }
+ }
+ };
+
+ radio1 = new Button(composite, SWT.RADIO);
+ radio1.addSelectionListener(selectionListener);
+
+ radio1.setText(Policy.bind("UpdateSyncAction.Only_update_resources_that_can_be_automatically_merged_3")); //$NON-NLS-1$
+
+ radio2 = new Button(composite, SWT.RADIO);
+ radio2.addSelectionListener(selectionListener);
+
+ radio2.setText(Policy.bind("UpdateSyncAction.Update_all_resources,_overwriting_local_changes_with_remote_contents_4")); //$NON-NLS-1$
+
+ // set initial state
+ radio1.setSelection(autoMerge);
+ radio2.setSelection(!autoMerge);
+ } else {
+ createWrappingLabel(composite, Policy.bind("UpdateSyncAction.Update_all_resources,_overwriting_local_changes_with_remote_contents_4"));
+ }
+ }
+
+ /**
+ * @return
+ */
+ private boolean hasAutomergableResources() {
+ return getSyncSet().hasNodes(getAutomergableFilter());
+ }
+
+ /**
+ * TODO: Could be a method on SyncResourceSet
+ * @param resources2
+ */
+ protected IResource[] getAllResources() {
+ SyncResource[] resources;
+ if (autoMerge) {
+ resources = getSyncSet().getNodes(getAutomergableFilter());
+ } else {
+ resources = getSyncSet().getSyncResources();
+ }
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ SyncResource resource = resources[i];
+ result.add(resource.getLocalResource());
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+
+ }
+
+ /**
+ * Set the filter used to determine if a change is automergable
+ * @param automergableFilter
+ */
+ public void setAutomergableFilter(SyncInfoFilter automergableFilter) {
+ this.automergableFilter = automergableFilter;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SubscriberAction#getSyncInfoFilter()
+ */
+ public SyncInfoFilter getAutomergableFilter() {
+ if (automergableFilter == null) {
+ // By default, filter for all incoming infos and automergable conflicting changes
+ automergableFilter = new OrSyncInfoFilter(new SyncInfoFilter[] {
+ new SyncInfoDirectionFilter(SyncInfo.INCOMING),
+ new AndSyncInfoFilter(new SyncInfoFilter[] {
+ new AutomergableFilter(),
+ new SyncInfoDirectionFilter(SyncInfo.CONFLICTING),
+ new SyncInfoChangeTypeFilter(SyncInfo.CHANGE)
+ })
+ });
+ }
+ return automergableFilter;
+ }
+
+ private void setAutomerge(boolean b) {
+ this.autoMerge = b;
+ setViewerInput();
+ }
+
+ public boolean getAutomerge() {
+ return autoMerge;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
+ */
+ protected void buttonPressed(int id) {
+ if (id == IDialogConstants.OK_ID) {
+ if (autoMerge) {
+ getSyncSet().selectNodes(getAutomergableFilter());
+ }
+ // TODO: remove those nodes not checked in the details area
+ }
+ super.buttonPressed(id);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/WorkspaceUpdateAction.java b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/WorkspaceUpdateAction.java
new file mode 100644
index 000000000..ef8b642d3
--- /dev/null
+++ b/bundles/org.eclipse.team.cvs.ui/src/org/eclipse/team/internal/ccvs/ui/subscriber/WorkspaceUpdateAction.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ccvs.ui.subscriber;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This action performs an update for the CVSWorkspaceSubscriber.
+ */
+public class WorkspaceUpdateAction extends SubscriberUpdateAction {
+
+ // used to indicate how conflicts are to be updated
+ private boolean onlyUpdateAutomergeable;
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberUpdateAction#performPrompting(org.eclipse.team.ui.sync.SyncResourceSet)
+ */
+ protected boolean performPrompting(SyncResourceSet syncSet) {
+ // If there are conflicts or outgoing changes in the syncSet, we need to warn the user.
+ onlyUpdateAutomergeable = false;
+ if (syncSet.hasConflicts() || syncSet.hasOutgoingChanges()) {
+ return promptForMergeableConflicts(syncSet);
+ }
+ return true;
+ }
+
+ /**
+ * Prompt for mergeable conflicts.
+ * Note: This method is designed to be overridden by test cases.
+ *
+ * @return 0 to cancel, 1 to only update mergeable conflicts, 2 to overwrite if unmergeable
+ */
+ protected boolean promptForMergeableConflicts(final SyncResourceSet syncSet) {
+ final int[] result = new int[] {Dialog.CANCEL};
+ final Shell shell = getShell();
+ shell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ UpdateDialog dialog = new UpdateDialog(shell, syncSet);
+ result[0] = dialog.open();
+ // Need to record the choice so that the update will be performed
+ // properly for automergable conflicts
+ onlyUpdateAutomergeable = dialog.getAutomerge();
+ }
+ });
+ return (result[0] == Dialog.OK);
+ }
+
+ /* (non-Javadoc)
+ *
+ * Return true for conflicting changes that are automergable if the user has chosen the
+ * appropriate operation.
+ *
+ * @see org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberUpdateAction#supportsShallowUpdateFor(org.eclipse.team.internal.ui.sync.views.SyncResource)
+ */
+ protected boolean supportsShallowUpdateFor(SyncResource changedNode) {
+ return (changedNode.getChangeDirection() == SyncInfo.CONFLICTING
+ && ((changedNode.getKind() & SyncInfo.CHANGE_MASK) == SyncInfo.CHANGE)
+ && onlyUpdateAutomergeable
+ && (changedNode.getKind() & SyncInfo.AUTOMERGE_CONFLICT) != 0);
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/ContributedSubscriberAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/ContributedSubscriberAction.java
new file mode 100644
index 000000000..e0140df75
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/ContributedSubscriberAction.java
@@ -0,0 +1,306 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IPluginDescriptor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.ui.TeamUIPlugin;
+import org.eclipse.team.internal.ui.sync.views.SubscriberInput;
+import org.eclipse.team.ui.sync.SubscriberAction;
+import org.eclipse.ui.IActionDelegate;
+import org.eclipse.ui.IActionDelegate2;
+import org.eclipse.ui.IObjectActionDelegate;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.help.WorkbenchHelp;
+
+/**
+ * This action delegates to an action delegate contributed through the plugin.xml
+ *
+ * <action
+ * label="%CVSWorkspaceSubscriber.commit.label"
+ * tooltip="%CVSWorkspaceSubscriber.commit.tooltip"
+ * class="org.eclipse.team.internal.ccvs.ui.subscriber.CommitAction"
+ * helpContextId="org.eclipse.team.cvs.ui.workspace_subscriber_commit_action"
+ * id="org.eclipse.team.ccvs.ui.CVSWorkspaceSubscriber.commit">
+ * </action>
+ */
+public class ContributedSubscriberAction extends SyncViewerAction {
+
+ private IActionDelegate delegate;
+ private IConfigurationElement element;
+ private IWorkbenchPart activePart;
+ private ISelection selection;
+ private SubscriberInput context;
+
+ /*
+ * NOTE: Code copied from WorkbenchPlugin.
+ *
+ * Creates an extension. If the extension plugin has not
+ * been loaded a busy cursor will be activated during the duration of
+ * the load.
+ *
+ * @param element the config element defining the extension
+ * @param classAttribute the name of the attribute carrying the class
+ * @returns the extension object
+ */
+ private static Object createExtension(final IConfigurationElement element, final String classAttribute) throws TeamException {
+ // If plugin has been loaded create extension.
+ // Otherwise, show busy cursor then create extension.
+ IPluginDescriptor plugin = element.getDeclaringExtension().getDeclaringPluginDescriptor();
+ if (plugin.isPluginActivated()) {
+ try {
+ return element.createExecutableExtension(classAttribute);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ } else {
+ final Object[] ret = new Object[1];
+ final TeamException[] exc = new TeamException[1];
+ BusyIndicator.showWhile(null, new Runnable() {
+ public void run() {
+ try {
+ ret[0] = element.createExecutableExtension(classAttribute);
+ } catch (CoreException e) {
+ exc[0] = TeamException.asTeamException(e);
+ }
+ }
+ });
+ if (exc[0] != null)
+ throw exc[0];
+ else
+ return ret[0];
+ }
+ }
+
+ private static void log(String message, TeamException e) {
+ if (message == null) {
+ message = e.getMessage();
+ System.err.println(message);
+ } else {
+ System.err.println(message + "\nReason:");
+ System.err.println(e.getStatus().getMessage());
+ }
+ TeamUIPlugin.log(new Status(IStatus.ERROR, TeamUIPlugin.ID, 0, message, e));
+ }
+
+ /**
+ * @param viewer
+ * @param element
+ */
+ public ContributedSubscriberAction(IViewPart viewPart, IConfigurationElement element) {
+ super(viewPart, element.getAttribute("label"));
+ this.element = element;
+ String tooltip = element.getAttribute("tooltip");
+ String helpContextId = element.getAttribute("helpContextId");
+ String id = element.getAttribute("id");
+ setToolTipText(tooltip);
+ setId(id);
+ if (helpContextId != null) {
+ WorkbenchHelp.setHelp(this, helpContextId);
+ }
+ }
+
+ /**
+ * Return the delegate action or null if not created yet
+ */
+ private IActionDelegate getDelegate() {
+ if (delegate == null) {
+ createDelegate();
+ }
+ return delegate;
+ }
+
+ private void createDelegate() {
+ if (delegate == null) {
+ try {
+ Object obj = createExtension(element, "class");
+ delegate = validateDelegate(obj);
+ initDelegate();
+ refreshEnablement();
+ } catch (TeamException e) {
+ String id = element.getAttribute("id");
+ log("Could not create action delegate for id: " + id, e); //$NON-NLS-1$
+ return;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private void refreshEnablement() {
+ if (delegate != null) {
+ delegate.selectionChanged(this, selection);
+ }
+ }
+
+ private void initDelegate() {
+ if (delegate instanceof IActionDelegate2)
+ ((IActionDelegate2)delegate).init(this);
+ if (delegate instanceof IObjectActionDelegate && activePart != null)
+ ((IObjectActionDelegate)delegate).setActivePart(this, activePart);
+ }
+
+
+ /*
+ * Validates the object is a delegate of the expected type. Subclasses can
+ * override to check for specific delegate types.
+ * <p>
+ * <b>Note:</b> Calls to the object are not allowed during this method.
+ * </p>
+ *
+ * @param obj a possible action delegate implementation
+ * @return the <code>IActionDelegate</code> implementation for the object
+ * @throws a <code>WorkbenchException</code> if not expect delegate type
+ */
+ private IActionDelegate validateDelegate(Object obj) throws TeamException {
+ if (obj instanceof IActionDelegate)
+ return (IActionDelegate)obj;
+ else
+ // TODO: Code in PluginAction was not NLSed. Should it be?
+ throw new TeamException("Action must implement IActionDelegate"); //$NON-NLS-1$
+ }
+
+ /**
+ * Sets the active part for the delegate.
+ * <p>
+ * This method will be called every time the action appears in a popup menu. The
+ * targetPart may change with each invocation.
+ * </p>
+ *
+ * @param action the action proxy that handles presentation portion of the action
+ * @param targetPart the new part target
+ */
+ public void setActivePart(IWorkbenchPart targetPart) {
+ activePart = targetPart;
+ if (delegate != null && delegate instanceof IObjectActionDelegate)
+ ((IObjectActionDelegate) delegate).setActivePart(this, activePart);
+ }
+
+ /**
+ * Handles selection change. If rule-based enabled is
+ * defined, it will be first to call it. If the delegate
+ * is loaded, it will also be given a chance.
+ */
+ public void selectionChanged(ISelection newSelection) {
+ // Update selection.
+ selection = newSelection;
+ if (selection == null)
+ selection = StructuredSelection.EMPTY;
+
+ // If the delegate can be loaded, do so.
+ // Otherwise, just update the enablement.
+ if (delegate == null && isOkToCreateDelegate())
+ createDelegate();
+ else
+ refreshEnablement();
+ }
+
+ /**
+ * The <code>SelectionChangedEventAction</code> implementation of this
+ * <code>ISelectionChangedListener</code> method calls
+ * <code>selectionChanged(IStructuredSelection)</code> when the selection is
+ * a structured one.
+ */
+ public void selectionChanged(SelectionChangedEvent event) {
+ ISelection sel = event.getSelection();
+ selectionChanged(sel);
+ }
+
+ /**
+ * The <code>SelectionChangedEventAction</code> implementation of this
+ * <code>ISelectionListener</code> method calls
+ * <code>selectionChanged(IStructuredSelection)</code> when the selection is
+ * a structured one. Subclasses may extend this method to react to the change.
+ */
+ public void selectionChanged(IWorkbenchPart part, ISelection sel) {
+ selectionChanged(sel);
+ }
+
+ /**
+ * Returns true if the declaring plugin has been loaded
+ * and there is no need to delay creating the delegate
+ * any more.
+ */
+ private boolean isOkToCreateDelegate() {
+ // test if the plugin has loaded
+ IPluginDescriptor plugin =
+ element.getDeclaringExtension().getDeclaringPluginDescriptor();
+ return plugin.isPluginActivated();
+ }
+
+ /* (non-Javadoc)
+ * Method declared on IAction.
+ */
+ public void run() {
+ runWithEvent(null);
+ }
+
+ /* (non-Javadoc)
+ * Method declared on IAction.
+ */
+ public void runWithEvent(Event event) {
+ // this message dialog is problematic.
+ if (delegate == null) {
+ // TODO: We should create the delegate earlier since the subscriber
+ // is already loaded by the sync view.
+ createDelegate();
+ if (delegate == null) {
+ MessageDialog.openInformation(
+ Display.getDefault().getActiveShell(),
+ "Information",
+ "Operation Not Available");
+ return;
+ }
+ if (!isEnabled()) {
+ MessageDialog.openInformation(
+ Display.getDefault().getActiveShell(),
+ "Information",
+ "Operation is disabled");
+ return;
+ }
+ }
+
+ if (event != null) {
+ if (delegate instanceof IActionDelegate2) {
+ ((IActionDelegate2)delegate).runWithEvent(this, event);
+ return;
+ }
+ }
+
+ delegate.run(this);
+ }
+
+ /*
+ * Set the context
+ * @param input
+ */
+ protected void setContext(SubscriberInput input) {
+ this.context = input;
+ IActionDelegate delegate = getDelegate();
+ if (delegate instanceof SubscriberAction) {
+ ((SubscriberAction)delegate).setSubscriber(context.getSubscriber());
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/FilterSyncViewerAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/FilterSyncViewerAction.java
new file mode 100644
index 000000000..5cf129eaa
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/FilterSyncViewerAction.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.team.internal.ui.sync.views.ChangeFiltersContentProvider;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.dialogs.ListSelectionDialog;
+
+/**
+ * This action prompts the user in order to obtain sync view change filters
+ */
+public class FilterSyncViewerAction extends SyncViewerAction {
+
+ SyncViewerChangeFilters filters;
+
+ public FilterSyncViewerAction(IViewPart viewPart, SyncViewerChangeFilters filters) {
+ super(viewPart, "Filter...");
+ this.filters = filters;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.action.IAction#run()
+ */
+ public void run() {
+ int[] filter = promptForFilter();
+ if (filter == null) return;
+ filters.refreshFilters();
+ }
+
+ /**
+ * @return
+ */
+ private int[] promptForFilter() {
+ ChangeFiltersContentProvider contentProvider = filters.getContentProvider();
+ ListSelectionDialog dialog =
+ new ListSelectionDialog(
+ getShell(),
+ this /* the input can be any object */,
+ contentProvider,
+ filters.getLabelProvider(),
+ "Select the change types to be shown");
+
+ dialog.setTitle("Synchronize View Filters");
+ dialog.setInitialSelections(contentProvider.getInitialSelections());
+ dialog.open();
+ int[] changeFilters = null;
+ if (dialog.getReturnCode() == Dialog.OK) {
+ Object[] results = dialog.getResult();
+ filters.setActiveFilters(results);
+ changeFilters = filters.getChangeFilters();
+ }
+ return changeFilters;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenInCompareAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenInCompareAction.java
new file mode 100644
index 000000000..dd6e77ed6
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenInCompareAction.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.sync.compare.SyncInfoCompareInput;
+import org.eclipse.team.internal.ui.sync.views.SyncSet;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IWorkbenchPage;
+
+/**
+ * OpenInCompareEditor
+ *
+ * TODO: Compare editor should implement IReusableEditor so that the non-dirty editor doesn't have to be closed
+ * and the compare editor finding should be cleaned up.
+ */
+public class OpenInCompareAction extends Action {
+
+ private SyncViewer viewer;
+
+ public OpenInCompareAction(SyncViewer viewer) {
+ this.viewer = viewer;
+ setText("Open With Compare Editor");
+ }
+
+ public void run() {
+ openEditor();
+ }
+
+ private void openEditor() {
+ CompareEditorInput input = getCompareInput();
+ if(input != null) {
+ IEditorPart editor = reuseCompareEditor((SyncInfoCompareInput)input);
+ CompareUI.openCompareEditor(input);
+ }
+ }
+
+ private CompareEditorInput getCompareInput() {
+ ISelection selection = viewer.getViewer().getSelection();
+ Object obj = ((IStructuredSelection)selection).getFirstElement();
+ SyncInfo info = SyncSet.getSyncInfo(obj);
+ if (info != null && info.getLocal() instanceof IFile) {
+ return new SyncInfoCompareInput(info);
+ }
+ return null;
+ }
+
+ private IEditorPart reuseCompareEditor(SyncInfoCompareInput input) {
+ IWorkbenchPage page = viewer.getSite().getPage();
+ IEditorReference[] editorRefs = page.getEditorReferences();
+
+ IEditorPart editor = page.findEditor(input);
+ if(editor == null) {
+ for (int i = 0; i < editorRefs.length; i++) {
+ IEditorPart part = editorRefs[i].getEditor(true);
+ if(part != null && part.getEditorInput() instanceof SyncInfoCompareInput) {
+ if(! part.isDirty()) {
+ page.closeEditor(part, true /*save changes if required */);
+ }
+ }
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenWithActionGroup.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenWithActionGroup.java
new file mode 100644
index 000000000..25245a420
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/OpenWithActionGroup.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.team.internal.ui.sync.views.SyncSet;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.ui.actions.OpenFileAction;
+import org.eclipse.ui.actions.OpenWithMenu;
+import org.eclipse.ui.views.navigator.ResourceNavigatorMessages;
+
+/**
+ * This is the action group for the open actions.
+ */
+public class OpenWithActionGroup extends SyncViewerActionGroup {
+
+ private OpenFileAction openFileAction;
+
+ public OpenWithActionGroup(SyncViewer viewer) {
+ super(viewer);
+ makeActions();
+ }
+
+ protected void makeActions() {
+ openFileAction = new OpenFileAction(getSyncView().getSite().getPage());
+ }
+
+ public void fillContextMenu(IMenuManager menu) {
+ IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
+ fillOpenWithMenu(menu, selection);
+ }
+
+ /**
+ * Adds the OpenWith submenu to the context menu.
+ *
+ * @param menu the context menu
+ * @param selection the current selection
+ */
+ private void fillOpenWithMenu(IMenuManager menu, IStructuredSelection selection) {
+
+ // Only supported if exactly one file is selected.
+ if (selection.size() != 1)
+ return;
+ Object element = selection.getFirstElement();
+ IResource resource = SyncSet.getIResource(element);
+ if (!(resource instanceof IFile)) {
+ return;
+ }
+ if(!((resource.exists()))) {
+ return;
+ }
+
+ openFileAction.selectionChanged(selection);
+ menu.add(openFileAction);
+
+ MenuManager submenu =
+ new MenuManager(ResourceNavigatorMessages.getString("ResourceNavigator.openWith")); //$NON-NLS-1$
+ submenu.add(new OpenWithMenu(getSyncView().getSite().getPage(), (IFile) resource));
+ menu.add(submenu);
+ }
+
+ /**
+ * Runs the default action (open file).
+ */
+ public void runDefaultAction(IStructuredSelection selection) {
+ Object element = selection.getFirstElement();
+ if (element instanceof IFile) {
+ openFileAction.selectionChanged(selection);
+ openFileAction.run();
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerAction.java
new file mode 100644
index 000000000..4d55274e2
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerAction.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IViewPart;
+
+/**
+ * This class acts as the superclass for all actions in the SyncViewer
+ */
+public abstract class SyncViewerAction extends Action {
+
+ private IViewPart viewPart;
+ private ISelection selection;
+
+ /**
+ * @param text
+ */
+ public SyncViewerAction(IViewPart viewPart, String label) {
+ super(label);
+ this.viewPart = viewPart;
+ }
+
+ public Shell getShell() {
+ return viewPart.getSite().getShell();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActionGroup.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActionGroup.java
new file mode 100644
index 000000000..dbad6f753
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActionGroup.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.team.internal.ui.sync.views.SubscriberInput;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.actions.ActionContext;
+import org.eclipse.ui.actions.ActionGroup;
+
+/**
+ * This class acts as the superclass fo all action groups that appear in the SyncViewer
+ */
+public abstract class SyncViewerActionGroup extends ActionGroup {
+
+ private SyncViewer syncView;
+
+ protected SyncViewerActionGroup(SyncViewer syncView) {
+ this.syncView = syncView;
+ }
+
+ /**
+ * Return the SyncViewer for this action group
+ * @return
+ */
+ public SyncViewer getSyncView() {
+ return syncView;
+ }
+
+ /**
+ * Save the state of the action group into the given IMemento
+ * @param memento
+ */
+ public void save(IMemento memento) {
+ }
+
+ /**
+ * Restore the state of the action group from the IMemento
+ * @param memento
+ */
+ public void restore(IMemento memento) {
+ }
+
+ public void setContext(ActionContext context) {
+ super.setContext(context);
+ initializeActions();
+ }
+
+ public void addContext(ActionContext context) {
+ }
+
+ public void removeContext(ActionContext context) {
+ }
+
+ protected void initializeActions() {
+ }
+
+ protected SubscriberInput getSubscriberContext() {
+ ActionContext input = getContext();
+ if(input != null) {
+ return (SubscriberInput)input.getInput();
+ }
+ return null;
+ }
+
+ /**
+ * Method invoked from a SyncViewerToolbarDropDownAction
+ *
+ * @param menu
+ */
+ public void fillMenu(SyncViewerToolbarDropDownAction action) {
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActions.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActions.java
new file mode 100644
index 000000000..f6c14d69e
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerActions.java
@@ -0,0 +1,481 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.AbstractTreeViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.internal.core.Policy;
+import org.eclipse.team.internal.ui.UIConstants;
+import org.eclipse.team.internal.ui.actions.TeamAction;
+import org.eclipse.team.internal.ui.sync.views.SubscriberInput;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.team.ui.ISharedImages;
+import org.eclipse.team.ui.TeamImages;
+import org.eclipse.team.ui.sync.AndSyncInfoFilter;
+import org.eclipse.team.ui.sync.PseudoConflictFilter;
+import org.eclipse.team.ui.sync.SyncInfoChangeTypeFilter;
+import org.eclipse.team.ui.sync.SyncInfoDirectionFilter;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.IWorkingSet;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.ActionContext;
+import org.eclipse.ui.actions.WorkingSetFilterActionGroup;
+
+/**
+ * This class managers the actions associated with the SyncViewer class.
+ */
+public class SyncViewerActions extends SyncViewerActionGroup {
+
+ // action groups for view filtering
+ private SyncViewerDirectionFilters directionsFilters;
+ private SyncViewerChangeFilters changeFilters;
+ private SyncViewerComparisonCriteria comparisonCriteria;
+ private SyncViewerSubscriberListActions subscriberInputs;
+ private SyncViewerSubscriberActions subscriberActions;
+
+ private WorkingSetFilterActionGroup workingSetGroup;
+ private OpenWithActionGroup openWithActionGroup;
+
+ private SyncViewerToolbarDropDownAction chooseSubscriberAction;
+ private ChooseComparisonCriteriaAction chooseComparisonCriteriaAction;
+
+ private IWorkingSet workingSet;
+
+ // other view actions
+ private Action collapseAll;
+ private Action refreshSelectionAction;
+ private Action refreshAllAction;
+ private Action toggleViewerType;
+ private Action open;
+ private ExpandAllAction expandAll;
+ private CancelSubscription cancelSubscription;
+
+ class RefreshAction extends Action {
+ private boolean refreshAll;
+ public RefreshAction(boolean refreshAll) {
+ this.refreshAll = refreshAll;
+ setText("Refresh with Repository");
+ setToolTipText("Refresh with the repository");
+ setImageDescriptor(TeamImages.getImageDescriptor(UIConstants.IMG_REFRESH_ENABLED));
+ setDisabledImageDescriptor(TeamImages.getImageDescriptor(UIConstants.IMG_REFRESH_DISABLED));
+ setHoverImageDescriptor(TeamImages.getImageDescriptor(UIConstants.IMG_REFRESH));
+ }
+ public void run() {
+ final SyncViewer view = getSyncView();
+ view.run(new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ monitor.beginTask(null, 100);
+ ActionContext context = getContext();
+ getResources(context.getSelection());
+ SubscriberInput input = (SubscriberInput)context.getInput();
+ IResource[] resources = getResources(context.getSelection());
+ if (refreshAll || resources.length == 0) {
+ // If no resources are selected, refresh all the subscriber roots
+ resources = input.roots();
+ }
+ input.getSubscriber().refresh(resources, IResource.DEPTH_INFINITE, Policy.subMonitorFor(monitor, 100));
+ } catch (TeamException e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+ private IResource[] getResources(ISelection selection) {
+ if(selection == null) {
+ return new IResource[0];
+ }
+ return (IResource[])TeamAction.getSelectedAdaptables(selection, IResource.class);
+ }
+ });
+ }
+ }
+
+ class CancelSubscription extends Action {
+ public CancelSubscription() {
+ setText("Cancel");
+ setToolTipText("Cancel the active synchronization target");
+ }
+ public void run() {
+ ActionContext context = getContext();
+ SubscriberInput input = (SubscriberInput)context.getInput();
+ input.getSubscriber().cancel();
+ }
+ public void updateTitle(SubscriberInput input) {
+ TeamSubscriber subscriber = input.getSubscriber();
+ if(subscriber.isCancellable()) {
+ setText("Cancel [" + subscriber.getName() +"]");
+ } else {
+ setText("Cancel");
+ }
+ setToolTipText("Cancel the active synchronization target");
+ }
+ }
+
+ class ExpandAllAction extends Action {
+ public ExpandAllAction() {
+ super("Expand All");
+ }
+ public void run() {
+ expandSelection();
+ }
+ public void update() {
+ setEnabled(getTreeViewer() != null && hasSelection());
+ }
+ protected void expandSelection() {
+ AbstractTreeViewer treeViewer = getTreeViewer();
+ if (treeViewer != null) {
+ ISelection selection = getSelection();
+ if (selection instanceof IStructuredSelection) {
+ Iterator elements = ((IStructuredSelection)selection).iterator();
+ while (elements.hasNext()) {
+ Object next = elements.next();
+ treeViewer.expandToLevel(next, AbstractTreeViewer.ALL_LEVELS);
+ }
+ }
+ }
+ }
+ private AbstractTreeViewer getTreeViewer() {
+ Viewer viewer = getSyncView().getViewer();
+ if (viewer instanceof AbstractTreeViewer) {
+ return (AbstractTreeViewer)viewer;
+ }
+ return null;
+ }
+ private ISelection getSelection() {
+ ActionContext context = getContext();
+ if (context == null) return null;
+ return getContext().getSelection();
+ }
+ private boolean hasSelection() {
+ ISelection selection = getSelection();
+ return (selection != null && !selection.isEmpty());
+ }
+ }
+
+ class CollapseAllAction extends Action {
+ public CollapseAllAction() {
+ super("Collapse All", TeamImages.getImageDescriptor(ISharedImages.IMG_COLLAPSE_ALL_ENABLED));
+ setToolTipText("Collapse all entries in the view");
+ setHoverImageDescriptor(TeamImages.getImageDescriptor(ISharedImages.IMG_COLLAPSE_ALL));
+ }
+ public void run() {
+ getSyncView().collapseAll();
+ }
+ }
+
+ class ToggleViewAction extends Action {
+ public ToggleViewAction(int initialState) {
+ setText("Toggle Tree/Table");
+ setToolTipText("Toggle Tree/Table");
+ setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
+ getImageDescriptor(org.eclipse.ui.ISharedImages.IMG_TOOL_COPY));
+ setChecked(initialState == SyncViewer.TREE_VIEW);
+ }
+ public void run() {
+ int viewerType;
+ if(toggleViewerType.isChecked()) {
+ viewerType = SyncViewer.TREE_VIEW;
+ } else {
+ viewerType = SyncViewer.TABLE_VIEW;
+ }
+ getSyncView().switchViewerType(viewerType);
+ }
+ }
+
+ class ChooseSubscriberAction extends SyncViewerToolbarDropDownAction {
+ public ChooseSubscriberAction(SyncViewerActionGroup actionGroup) {
+ super(actionGroup);
+ setText("Select Subscriber");
+ setToolTipText("Select Subscriber");
+ setImageDescriptor(TeamImages.getImageDescriptor(UIConstants.IMG_SITE_ELEMENT));
+ }
+ }
+
+ class ChooseComparisonCriteriaAction extends SyncViewerToolbarDropDownAction {
+ public ChooseComparisonCriteriaAction(SyncViewerActionGroup actionGroup) {
+ super(actionGroup);
+ setText("Select Comparison Criteria");
+ setToolTipText("Select Comparison Criteria");
+ setImageDescriptor(TeamImages.getImageDescriptor(UIConstants.IMG_CONTENTS));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#updateActionBars()
+ */
+ public void updateActionBars() {
+ super.updateActionBars();
+ changeFilters.updateActionBars();
+ directionsFilters.updateActionBars();
+ comparisonCriteria.updateActionBars();
+ subscriberInputs.updateActionBars();
+ subscriberActions.updateActionBars();
+
+ expandAll.update();
+ }
+
+ public SyncViewerActions(SyncViewer viewer) {
+ super(viewer);
+ createActions();
+ }
+
+ private void createActions() {
+ // initialize action groups
+ SyncViewer syncView = getSyncView();
+ directionsFilters = new SyncViewerDirectionFilters(syncView, this);
+ changeFilters = new SyncViewerChangeFilters(syncView, this);
+ subscriberActions = new SyncViewerSubscriberActions(syncView);
+
+ // initialize the dropdown for choosing a subscriber
+ subscriberInputs = new SyncViewerSubscriberListActions(syncView);
+ chooseSubscriberAction = new ChooseSubscriberAction(subscriberInputs);
+
+ // initialize the dropdown for choosing a comparison criteria
+ comparisonCriteria = new SyncViewerComparisonCriteria(syncView);
+ chooseComparisonCriteriaAction = new ChooseComparisonCriteriaAction(comparisonCriteria);
+
+ // initialize other actions
+ refreshAllAction = new RefreshAction(true);
+ refreshAllAction.setEnabled(false);
+ refreshSelectionAction = new RefreshAction(false);
+ refreshSelectionAction.setEnabled(false);
+
+ collapseAll = new CollapseAllAction();
+ expandAll = new ExpandAllAction();
+ cancelSubscription = new CancelSubscription();
+
+ toggleViewerType = new ToggleViewAction(SyncViewer.TABLE_VIEW);
+ open = new OpenInCompareAction(syncView);
+
+ IPropertyChangeListener workingSetUpdater = new IPropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent event) {
+ String property = event.getProperty();
+
+ if (WorkingSetFilterActionGroup.CHANGE_WORKING_SET.equals(property)) {
+ Object newValue = event.getNewValue();
+
+ if (newValue instanceof IWorkingSet) {
+ setWorkingSet((IWorkingSet) newValue);
+ }
+ else
+ if (newValue == null) {
+ setWorkingSet(null);
+ }
+ }
+ }
+ };
+ workingSetGroup = new WorkingSetFilterActionGroup(syncView.getSite().getShell(), workingSetUpdater);
+ openWithActionGroup = new OpenWithActionGroup(getSyncView());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#fillActionBars(org.eclipse.ui.IActionBars)
+ */
+ public void fillActionBars(IActionBars actionBars) {
+ super.fillActionBars(actionBars);
+
+ IToolBarManager manager = actionBars.getToolBarManager();
+ manager.add(chooseSubscriberAction);
+ manager.add(chooseComparisonCriteriaAction);
+ manager.add(new Separator());
+ directionsFilters.fillActionBars(actionBars);
+ manager.add(new Separator());
+ manager.add(refreshAllAction);
+ manager.add(collapseAll);
+ manager.add(toggleViewerType);
+
+ IMenuManager dropDownMenu = actionBars.getMenuManager();
+ workingSetGroup.fillActionBars(actionBars);
+ dropDownMenu.add(new Separator());
+ changeFilters.fillContextMenu(dropDownMenu);
+ dropDownMenu.add(new Separator());
+ dropDownMenu.add(cancelSubscription);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#fillContextMenu(org.eclipse.jface.action.IMenuManager)
+ */
+ public void fillContextMenu(IMenuManager manager) {
+ super.fillContextMenu(manager);
+
+ manager.add(open);
+ openWithActionGroup.fillContextMenu(manager);
+ manager.add(new Separator());
+ manager.add(expandAll);
+ manager.add(new Separator());
+ manager.add(refreshSelectionAction);
+ // Subscriber menus go here
+ subscriberActions.fillContextMenu(manager);
+ // Other plug-ins can contribute there actions here
+ manager.add(new Separator("Additions"));
+ }
+
+ public void refreshFilters() {
+ final SubscriberInput input = getSubscriberContext();
+ if(input != null) {
+ try {
+ input.setFilter(new AndSyncInfoFilter(
+ new SyncInfoFilter[] {
+ new SyncInfoDirectionFilter(directionsFilters.getDirectionFilter()),
+ new SyncInfoChangeTypeFilter(changeFilters.getChangeFilters()),
+ new PseudoConflictFilter()
+ }), new NullProgressMonitor());
+ } catch (TeamException e) {
+
+ }
+ }
+ }
+
+ public void open() {
+ open.run();
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.actions.SyncViewerActionGroup#restore(org.eclipse.ui.IMemento)
+ */
+ public void restore(IMemento memento) {
+ if(memento == null) return;
+ super.restore(memento);
+ changeFilters.restore(memento);
+ directionsFilters.restore(memento);
+ comparisonCriteria.restore(memento);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.actions.SyncViewerActionGroup#save(org.eclipse.ui.IMemento)
+ */
+ public void save(IMemento memento) {
+ if(memento == null) return;
+ super.save(memento);
+ changeFilters.save(memento);
+ directionsFilters.save(memento);
+ comparisonCriteria.save(memento);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.actions.SyncViewerActionGroup#initializeActions()
+ */
+ protected void initializeActions() {
+ SubscriberInput input = getSubscriberContext();
+ refreshAllAction.setEnabled(input != null);
+ refreshSelectionAction.setEnabled(input != null);
+ cancelSubscription.setEnabled(input.getSubscriber().isCancellable());
+ cancelSubscription.updateTitle(input);
+ // This is invoked before the subscriber input is initialized
+ if (input.getWorkingSet() == null) {
+ // set the input to use the last selected working set
+ input.setWorkingSet(getWorkingSet());
+ } else {
+ // set the menu to select the set from the input
+ // the callback will not prepare the input since the set
+ // for the input is the same as the one being passed to the menu
+ workingSetGroup.setWorkingSet(getWorkingSet());
+ }
+
+ // refresh the selecte filter
+ refreshFilters();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#setContext(org.eclipse.ui.actions.ActionContext)
+ */
+ public void setContext(ActionContext context) {
+ changeFilters.setContext(context);
+ directionsFilters.setContext(context);
+ comparisonCriteria.setContext(context);
+ subscriberInputs.setContext(context);
+ subscriberActions.setContext(context);
+ openWithActionGroup.setContext(context);
+
+ // causes initializeActions to be called. Must be called after
+ // setting the context for contained groups.
+ super.setContext(context);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#setContext(org.eclipse.ui.actions.ActionContext)
+ */
+ public void addContext(ActionContext context) {
+ subscriberInputs.addContext(context);
+ }
+
+ public void removeContext(ActionContext context) {
+ subscriberInputs.removeContext(context);
+ }
+
+ /*
+ * Get the selected working set from the subscriber input
+ * @return
+ */
+ private IWorkingSet getWorkingSet() {
+ SubscriberInput input = getSubscriberContext();
+ // There's no subscriber input so use the last selected workingSet
+ if (input == null) return workingSet;
+ IWorkingSet set = input.getWorkingSet();
+ // There's no subscriber working set so use the last selected workingSet
+ if (set == null ) return workingSet;
+ return set;
+ }
+
+ /**
+ * @param set
+ */
+ protected void setWorkingSet(IWorkingSet set) {
+ // Keep track of the last working set selected
+ if (set != null) workingSet = set;
+ final SubscriberInput input = getSubscriberContext();
+ if (input == null) return;
+ if (workingSetsEqual(input.getWorkingSet(), set)) return;
+ input.setWorkingSet(set);
+ getSyncView().run(new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ // when the working set changes, recalculate the entire sync set based on
+ // the new input.
+ input.prepareInput(monitor);
+ getSyncView().updateTitle();
+ } catch (TeamException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * @param set
+ * @param set2
+ * @return
+ */
+ private boolean workingSetsEqual(IWorkingSet set, IWorkingSet set2) {
+ if (set == null && set2 == null) return true;
+ if (set == null || set2 == null) return false;
+ return set.equals(set2);
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerChangeFilters.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerChangeFilters.java
new file mode 100644
index 000000000..42651a21d
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerChangeFilters.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.sync.views.ChangeFiltersContentProvider;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.ui.IMemento;
+
+/**
+ * This class provides a set of actions that support sync set filtering by
+ * change type. Changing the change type only requires setting a new
+ * filter on the sync set.
+ */
+public class SyncViewerChangeFilters extends SyncViewerActionGroup {
+
+ private static final String MEMENTO_KEY_PREFIX = "SyncViewerChangeFilters";
+
+ // array of actions for filtering by change type (additions, deletions and changes)
+ private ChangeFilterAction[] actions;
+
+ private FilterSyncViewerAction filterAction;
+ private SyncViewerActions refreshGroup;
+
+ /**
+ * Action for filtering by change type.
+ */
+ class ChangeFilterAction extends Action {
+ // The SyncInfo change constant associated with the change type
+ private int changeFilter;
+ public ChangeFilterAction(String title, int changeFilter) {
+ super(title);
+ this.changeFilter = changeFilter;
+ }
+ public void run() {
+ }
+ /**
+ * @return
+ */
+ public int getChangeFilter() {
+ return changeFilter;
+ }
+ }
+
+ protected SyncViewerChangeFilters(SyncViewer viewer, SyncViewerActions refreshGroup) {
+ super(viewer);
+ this.refreshGroup = refreshGroup;
+ createActions();
+ }
+
+ private void createActions() {
+ ChangeFilterAction additions = new ChangeFilterAction("Show Additions", SyncInfo.ADDITION);
+ additions.setChecked(true);
+ ChangeFilterAction deletions = new ChangeFilterAction("Show Deletions", SyncInfo.DELETION);
+ deletions.setChecked(true);
+ ChangeFilterAction changes = new ChangeFilterAction("Show Changes", SyncInfo.CHANGE);
+ changes.setChecked(true);
+ actions = new ChangeFilterAction[] { additions, deletions, changes };
+
+ filterAction = new FilterSyncViewerAction(getSyncView(), this);
+ }
+
+ /**
+ * Get the current set of active change filters
+ */
+ public int[] getChangeFilters() {
+ // Determine how many change types are checked
+ int count = 0;
+ for (int i = 0; i < actions.length; i++) {
+ ChangeFilterAction action = actions[i];
+ if (action.isChecked()) {
+ count++;
+ }
+ }
+ // Create an array of checked change types
+ int[] changeFilters = new int[count];
+ count = 0;
+ for (int i = 0; i < actions.length; i++) {
+ ChangeFilterAction action = actions[i];
+ if (action.isChecked()) {
+ changeFilters[count++] = action.getChangeFilter();
+ }
+ }
+ return changeFilters;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#fillContextMenu(org.eclipse.jface.action.IMenuManager)
+ */
+ public void fillContextMenu(IMenuManager menu) {
+ super.fillContextMenu(menu);
+ menu.add(filterAction);
+ }
+
+ /**
+ * Return all the actions for filtering by change type
+ * @return
+ */
+ public Action[] getFilters() {
+ return actions;
+ }
+
+ /**
+ * Return all the active change types
+ * @return
+ */
+ public Action[] getActiveFilters() {
+ List result = new ArrayList();
+ for (int i = 0; i < actions.length; i++) {
+ Action action = actions[i];
+ if (action.isChecked()) {
+ result.add(action);
+ }
+ }
+ return (Action[]) result.toArray(new Action[result.size()]);
+ }
+
+ /**
+ * Change the active change types to those in the provided array
+ * @param results
+ */
+ public void setActiveFilters(Object[] results) {
+ for (int i = 0; i < actions.length; i++) {
+ Action action = actions[i];
+ boolean active = false;
+ for (int j = 0; j < results.length; j++) {
+ Object object = results[j];
+ if (object == action) {
+ active = true;
+ break;
+ }
+ }
+ action.setChecked(active);
+ }
+ }
+
+ /**
+ *
+ */
+ public ILabelProvider getLabelProvider() {
+ return new LabelProvider() {
+ public String getText(Object element) {
+ if (element instanceof Action) {
+ return ((Action)element).getText();
+ }
+ return super.getText(element);
+ }
+ };
+ }
+
+ /**
+ *
+ */
+ public ChangeFiltersContentProvider getContentProvider() {
+ return new ChangeFiltersContentProvider(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.actions.SyncViewerActionGroup#restore(org.eclipse.ui.IMemento)
+ */
+ public void restore(IMemento memento) {
+ super.restore(memento);
+ // Uncheck everything
+ setActiveFilters(new ChangeFilterAction[0]);
+ // Check those in the memento
+ Integer i;
+ int count = 0;
+ do {
+ i = memento.getInteger(MEMENTO_KEY_PREFIX + "." + count);
+ if (i != null) {
+ count++;
+ actions[i.intValue()].setChecked(true);
+ }
+ } while (i != null);
+
+ // Make sure at least one is checked
+ if (count == 0) {
+ setActiveFilters(actions);
+ }
+ }
+
+ /* (non-Javadoc)
+ *
+ * Save each active change filter as follows:
+ *
+ * MEMENTO_KEY_PREFIX.0 = ?
+ * MEMENTO_KEY_PREFIX.1 = ?
+ *
+ * @see org.eclipse.team.ccvs.syncviews.actions.SyncViewerActionGroup#save(org.eclipse.ui.IMemento)
+ */
+ public void save(IMemento memento) {
+ super.save(memento);
+ int count = 0;
+ for (int i = 0; i < actions.length; i++) {
+ Action action = actions[i];
+ if (action.isChecked()) {
+ memento.putInteger(MEMENTO_KEY_PREFIX + "." + count++, i);
+ }
+ }
+ }
+
+ public SyncViewerActions getRefreshGroup() {
+ return refreshGroup;
+ }
+
+ public void refreshFilters() {
+ getRefreshGroup().refreshFilters();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerComparisonCriteria.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerComparisonCriteria.java
new file mode 100644
index 000000000..43a49f5e9
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerComparisonCriteria.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.ComparisonCriteria;
+import org.eclipse.team.internal.ui.sync.views.SubscriberInput;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+
+/**
+ * This action group allows the user to choose one or more comparison critera
+ * to be applied to a comparison
+ */
+public class SyncViewerComparisonCriteria extends SyncViewerActionGroup {
+
+ private static final String MEMENTO_KEY = "SelectedComparisonCriteria";
+
+ private ComparisonCriteria[] criteria;
+ private ComparisonCriteriaAction[] actions;
+
+ /**
+ * Action for filtering by change type.
+ */
+ class ComparisonCriteriaAction extends Action {
+ private ComparisonCriteria criteria;
+ public ComparisonCriteriaAction(ComparisonCriteria criteria) {
+ super(criteria.getName());
+ this.criteria = criteria;
+ }
+ public void run() {
+ SyncViewerComparisonCriteria.this.activate(this);
+ }
+ public ComparisonCriteria getComparisonCriteria() {
+ return criteria;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.actions.SyncViewerActionGroup#fillMenu(org.eclipse.team.internal.ui.sync.actions.SyncViewerToolbarDropDownAction)
+ */
+ public void fillMenu(SyncViewerToolbarDropDownAction dropDown) {
+ super.fillMenu(dropDown);
+ if(getSubscriberContext() != null) {
+ for (int i = 0; i < actions.length; i++) {
+ ComparisonCriteriaAction action = actions[i];
+ dropDown.add(action);
+ }
+ }
+ }
+
+ public SyncViewerComparisonCriteria(SyncViewer syncView) {
+ super(syncView);
+ setContext(null);
+ }
+
+ /**
+ * @param action
+ */
+ public void activate(final ComparisonCriteriaAction activatedAction) {
+ for (int i = 0; i < actions.length; i++) {
+ ComparisonCriteriaAction action = actions[i];
+ action.setChecked(activatedAction == action);
+ }
+ final SyncViewer view = getSyncView();
+ view.run(new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ // when the comparison criteria changes, recalculate the entire sync set based on
+ // the new input.
+ SubscriberInput input = getSubscriberContext();
+ input.getSubscriber().setCurrentComparisonCriteria(activatedAction.getComparisonCriteria().getId());
+ input.prepareInput(monitor);
+ } catch (TeamException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ });
+ }
+
+ public void initializeActions() {
+ SubscriberInput input = getSubscriberContext();
+ if(input != null) {
+ this.criteria = input.getSubscriber().getComparisonCriterias();
+ this.actions = new ComparisonCriteriaAction[criteria.length];
+ for (int i = 0; i < criteria.length; i++) {
+ ComparisonCriteria c = criteria[i];
+ actions[i] = new ComparisonCriteriaAction(c);
+ actions[i].setChecked(c == getSyncView().getInput().getSubscriber().getCurrentComparisonCriteria());
+ }
+ } else {
+ // there aren't any comparison criterias to show!
+ this.actions = null;
+ this.criteria = null;
+
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#fillContextMenu(org.eclipse.jface.action.IMenuManager)
+ */
+ public void fillContextMenu(IMenuManager menu) {
+ super.fillContextMenu(menu);
+ if(getSubscriberContext() != null) {
+ for (int i = 0; i < actions.length; i++) {
+ ComparisonCriteriaAction action = actions[i];
+ menu.add(action);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberActions.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberActions.java
new file mode 100644
index 000000000..7caef40bc
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberActions.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.team.internal.ui.TeamUIPlugin;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+
+/**
+ * This class manages the actions contributed by the subscriber.
+ */
+public class SyncViewerSubscriberActions extends SyncViewerActionGroup {
+
+ // cache of the subscriber actins
+ private HashMap definitions; // Subscriber class name -> SubscriberAction[]
+ private ContributedSubscriberAction[] actions;
+
+ /**
+ * @param syncView
+ */
+ protected SyncViewerSubscriberActions(SyncViewer syncView) {
+ super(syncView);
+ loadDefinitions();
+ }
+
+ private void loadDefinitions() {
+ IExtensionPoint point = Platform.getPluginRegistry().getExtensionPoint(TeamUIPlugin.ID, TeamUIPlugin.PT_SUBSCRIBER_MENUS);
+ IExtension[] types = point.getExtensions();
+ definitions = new HashMap(types.length);
+ for (int i = 0; i < types.length; i++)
+ loadDefinitions(types[i]);
+ }
+
+ private void loadDefinitions(IExtension type) {
+ IConfigurationElement[] elements = type.getConfigurationElements();
+ for (int i = 0; i < elements.length; i++) {
+ IConfigurationElement element = elements[i];
+ String subscriberName = getSubscriberClassName(element);
+ if (subscriberName != null) {
+ definitions.put(getSubscriberClassName(element), createActions(element));
+ }
+ }
+ }
+
+ /**
+ * @param element
+ * @return
+ */
+ private ContributedSubscriberAction[] createActions(IConfigurationElement element) {
+ IConfigurationElement[] children = element.getChildren();
+ List result = new ArrayList();
+ for (int i = 0; i < children.length; i++) {
+ IConfigurationElement actionDefinition = children[i];
+ ContributedSubscriberAction action = new ContributedSubscriberAction(getSyncView(), actionDefinition);
+ result.add(action);
+ }
+ return (ContributedSubscriberAction[]) result.toArray(new ContributedSubscriberAction[result.size()]);
+ }
+
+ /**
+ * @param extension
+ * @return
+ */
+ private String getSubscriberClassName(IConfigurationElement element) {
+ return (String)element.getAttribute("subscriberClass");
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.actions.ActionGroup#fillContextMenu(org.eclipse.jface.action.IMenuManager)
+ */
+ public void fillContextMenu(IMenuManager menu) {
+ super.fillContextMenu(menu);
+ if (actions == null || actions.length == 0) return;
+ menu.add(new Separator());
+ ISelection selection = getSyncView().getSelection();
+ for (int i = 0; i < actions.length; i++) {
+ ContributedSubscriberAction action = actions[i];
+ action.setActivePart(getSyncView().getSite().getPage().getActivePart());
+ action.setContext(getSubscriberContext());
+ action.selectionChanged(selection);
+ menu.add(action);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.actions.SyncViewerActionGroup#initializeActions()
+ */
+ protected void initializeActions() {
+ super.initializeActions();
+ String className = getSubscriberContext().getSubscriber().getClass().getName();
+ actions = (ContributedSubscriberAction[])definitions.get(className);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberListActions.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberListActions.java
new file mode 100644
index 000000000..68404f0d2
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerSubscriberListActions.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.team.internal.ui.sync.views.SubscriberInput;
+import org.eclipse.team.internal.ui.sync.views.SyncViewer;
+import org.eclipse.ui.actions.ActionContext;
+
+/**
+ * SyncViewerSubscriberListActions
+ */
+public class SyncViewerSubscriberListActions extends SyncViewerActionGroup {
+
+ private static final String MEMENTO_KEY = "SelectedComparisonCriteria";
+
+ // {QualifiedName:subscriber id -> SubscriberInput}
+ private Map actions = new HashMap();
+ private SubscriberInput activeInput = null;
+
+ /**
+ * Action for filtering by change type.
+ */
+ class SwitchSubscriberAction extends Action {
+ private SubscriberInput input;
+ public SwitchSubscriberAction(SubscriberInput input) {
+ super(input.getSubscriber().getName());
+ this.input = input;
+ }
+ public void run() {
+ SyncViewerSubscriberListActions.this.activate(this);
+ }
+ public SubscriberInput getSubscriberInput() {
+ return input;
+ }
+ }
+
+ public SyncViewerSubscriberListActions(SyncViewer syncView) {
+ super(syncView);
+ setContext(null);
+ }
+
+ public void activate(SwitchSubscriberAction activatedAction) {
+ if(activeInput == null || ! activatedAction.getSubscriberInput().getSubscriber().getId().equals(activeInput.getSubscriber().getId())) {
+ for (Iterator it = actions.values().iterator(); it.hasNext();) {
+ SwitchSubscriberAction action = (SwitchSubscriberAction) it.next();
+ action.setChecked(activatedAction == action);
+ }
+ final SyncViewer view = getSyncView();
+ view.initializeSubscriberInput(activatedAction.getSubscriberInput());
+ } else {
+ activatedAction.setChecked(true);
+ }
+ }
+
+ /*
+ * Called when a context is enabled for the view.
+ * (non-Javadoc)
+ * @see SyncViewerActionGroup#initializeActions()
+ */
+ public void initializeActions() {
+ SubscriberInput input = getSubscriberContext();
+ if (input != null) {
+ for (Iterator it = actions.values().iterator(); it.hasNext();) {
+ SwitchSubscriberAction action =
+ (SwitchSubscriberAction) it.next();
+ boolean checked = action.getSubscriberInput().getSubscriber().getId().equals(
+ input.getSubscriber().getId());
+ action.setChecked(checked);
+ if(checked) {
+ activeInput = input;
+ }
+ }
+ }
+ }
+
+ /*
+ * Checking of the currently active subscriber input is done when the context is set
+ * in the initializeActions method.
+ * (non-Javadoc)
+ * @see fillContextMenu(org.eclipse.jface.action.IMenuManager)
+ */
+ public void fillContextMenu(IMenuManager menu) {
+ super.fillContextMenu(menu);
+ if (! actions.isEmpty()) {
+ for (Iterator it = actions.values().iterator(); it.hasNext();) {
+ SwitchSubscriberAction action = (SwitchSubscriberAction) it.next();
+ menu.add(action);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.actions.SyncViewerActionGroup#addContext(org.eclipse.ui.actions.ActionContext)
+ */
+ public void addContext(ActionContext context) {
+ boolean enableFirstContext = actions.isEmpty();
+ SubscriberInput input = (SubscriberInput)context.getInput();
+ SwitchSubscriberAction action = new SwitchSubscriberAction(input);
+ actions.put(input.getSubscriber().getId(), action);
+ if(enableFirstContext) {
+ activate(action);
+ }
+ }
+
+ /*
+ * Method to add menu items to a toolbar drop down action
+ */
+ public void fillMenu(SyncViewerToolbarDropDownAction dropDown) {
+ super.fillMenu(dropDown);
+ if (! actions.isEmpty()) {
+ for (Iterator it = actions.values().iterator(); it.hasNext();) {
+ SwitchSubscriberAction action = (SwitchSubscriberAction) it.next();
+ dropDown.add(action);
+ }
+ }
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.actions.SyncViewerActionGroup#removeContext(org.eclipse.ui.actions.ActionContext)
+ */
+ public void removeContext(ActionContext context) {
+ SubscriberInput input = (SubscriberInput)context.getInput();
+ actions.remove(input.getSubscriber().getId());
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerToolbarDropDownAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerToolbarDropDownAction.java
new file mode 100644
index 000000000..6458d900f
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/actions/SyncViewerToolbarDropDownAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.actions;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IMenuCreator;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Menu;
+
+/**
+ * This class allows SyncViewerActionGroups to be place in a toolbar as
+ * drop down menus
+ */
+public class SyncViewerToolbarDropDownAction extends Action implements IMenuCreator {
+
+ SyncViewerActionGroup actionGroup;
+ private Menu fMenu;
+
+ /**
+ *
+ */
+ public SyncViewerToolbarDropDownAction(SyncViewerActionGroup actionGroup) {
+ this.actionGroup = actionGroup;
+ setMenuCreator(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.action.IMenuCreator#dispose()
+ */
+ public void dispose() {
+ if (fMenu != null) {
+ fMenu.dispose();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.action.IMenuCreator#getMenu(org.eclipse.swt.widgets.Control)
+ */
+ public Menu getMenu(Control parent) {
+ // TODO: The menu is recreated each time. Another possibility would be to
+ // cache the menu and reset it at the appropriate time
+ if (fMenu != null)
+ fMenu.dispose();
+
+ fMenu= new Menu(parent);
+ fillMenu();
+ return fMenu;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.action.IMenuCreator#getMenu(org.eclipse.swt.widgets.Menu)
+ */
+ public Menu getMenu(Menu parent) {
+ return null;
+ }
+
+ private void fillMenu() {
+ actionGroup.fillMenu(this);
+ }
+
+ public void add(Action action) {
+ ActionContributionItem item= new ActionContributionItem(action);
+ item.fill(getMenu(), -1);
+ }
+
+ public Menu getMenu() {
+ return fMenu;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.action.IAction#run()
+ */
+ public void run() {
+ // TODO Should show all actions in a dialog or something like that
+ super.run();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/LocalResourceTypedElement.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/LocalResourceTypedElement.java
new file mode 100644
index 000000000..4cc20e5c4
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/LocalResourceTypedElement.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.compare;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.compare.IEditableContent;
+import org.eclipse.compare.IStreamContentAccessor;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.ResourceNode;
+import org.eclipse.compare.internal.BufferedResourceNode;
+import org.eclipse.compare.structuremergeviewer.IStructureComparator;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * LocalResourceTypedElement
+ */
+public class LocalResourceTypedElement extends ResourceNode {
+
+ private boolean fDirty= false;
+ private IFile fDeleteFile;
+
+ /**
+ * Creates a <code>ResourceNode</code> for the given resource.
+ *
+ * @param resource the resource
+ */
+ public LocalResourceTypedElement(IResource resource) {
+ super(resource);
+ }
+
+ protected IStructureComparator createChild(IResource child) {
+ return new LocalResourceTypedElement(child);
+ }
+
+ public void setContent(byte[] contents) {
+ fDirty= true;
+ super.setContent(contents);
+ }
+
+ /**
+ * Commits buffered contents to resource.
+ */
+ public void commit(IProgressMonitor pm) throws CoreException {
+ if (fDirty) {
+
+ if (fDeleteFile != null) {
+ fDeleteFile.delete(true, true, pm);
+ return;
+ }
+
+ IResource resource= getResource();
+ if (resource instanceof IFile) {
+ ByteArrayInputStream is= new ByteArrayInputStream(getContent());
+ try {
+ IFile file= (IFile) resource;
+ if (file.exists())
+ file.setContents(is, false, true, pm);
+ else
+ file.create(is, false, pm);
+ fDirty= false;
+ } finally {
+ if (is != null)
+ try {
+ is.close();
+ } catch(IOException ex) {
+ }
+ }
+ }
+ }
+ }
+
+ public ITypedElement replace(ITypedElement child, ITypedElement other) {
+
+ if (child == null) { // add resource
+ // create a node without a resource behind it!
+ IResource resource= getResource();
+ if (resource instanceof IFolder) {
+ IFolder folder= (IFolder) resource;
+ IFile file= folder.getFile(other.getName());
+ child= new BufferedResourceNode(file);
+ }
+ }
+
+ if (other == null) { // delete resource
+ IResource resource= getResource();
+ if (resource instanceof IFolder) {
+ IFolder folder= (IFolder) resource;
+ IFile file= folder.getFile(child.getName());
+ if (file != null && file.exists()) {
+ fDeleteFile= file;
+ fDirty= true;
+ }
+ }
+ return null;
+ }
+
+ if (other instanceof IStreamContentAccessor && child instanceof IEditableContent) {
+ IEditableContent dst= (IEditableContent) child;
+
+ try {
+ InputStream is= ((IStreamContentAccessor)other).getContents();
+ byte[] bytes= readBytes(is);
+ if (bytes != null)
+ dst.setContent(bytes);
+ } catch (CoreException ex) {
+ }
+ }
+ return child;
+ }
+
+ public static byte[] readBytes(InputStream in) {
+ ByteArrayOutputStream bos= new ByteArrayOutputStream();
+ try {
+ while (true) {
+ int c= in.read();
+ if (c == -1)
+ break;
+ bos.write(c);
+ }
+
+ } catch (IOException ex) {
+ return null;
+
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException x) {
+ }
+ }
+ try {
+ bos.close();
+ } catch (IOException x) {
+ }
+ }
+ return bos.toByteArray();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/RemoteResourceTypedElement.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/RemoteResourceTypedElement.java
new file mode 100644
index 000000000..aaddc0438
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/RemoteResourceTypedElement.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.compare;
+
+import java.io.InputStream;
+
+import org.eclipse.compare.BufferedContent;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.compare.IEditableContent;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+
+/**
+ * RemoteResourceTypedElement
+ */
+public class RemoteResourceTypedElement extends BufferedContent implements ITypedElement, IEditableContent {
+
+ private IRemoteResource remote;
+ private boolean editable;
+
+ /**
+ * Creates a new content buffer for the given team node.
+ */
+ RemoteResourceTypedElement(IRemoteResource remote) {
+ this.remote = remote;
+ this.editable = false;
+ }
+
+ public Image getImage() {
+ return CompareUI.getImage(getType());
+ }
+
+ public String getName() {
+ return remote.getName();
+ }
+
+ public String getType() {
+ if (remote.isContainer()) {
+ return ITypedElement.FOLDER_TYPE;
+ }
+ String name = getName();
+ if (name != null) {
+ int index = name.lastIndexOf('.');
+ if (index == -1)
+ return ""; //$NON-NLS-1$
+ if (index == (name.length() - 1))
+ return ""; //$NON-NLS-1$
+ return name.substring(index + 1);
+ }
+ return ITypedElement.FOLDER_TYPE;
+ }
+
+ /**
+ * Returns true if this object can be modified.
+ * If it returns <code>false</code> the other methods must not be called.
+ *
+ * @return <code>true</code> if this object can be modified.
+ */
+ public boolean isEditable() {
+ return editable;
+ }
+
+ /**
+ * This is not the definitive API!
+ * This method is called on a parent to
+ * - add a child,
+ * - remove a child,
+ * - copy the contents of a child
+ *
+ * What to do is encoded in the two arguments as follows:
+ * add: child == null other != null
+ * remove: child != null other == null
+ * copy: child != null other != null
+ */
+ public ITypedElement replace(ITypedElement child, ITypedElement other) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see BufferedContent#createStream()
+ */
+ protected InputStream createStream() throws CoreException {
+ if (remote != null && !remote.isContainer()) {
+ try {
+ return remote.getContents(new NullProgressMonitor());
+ } catch (TeamException exception) {
+ // The remote resource has gone.
+ return null;
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInput.java
new file mode 100644
index 000000000..78c5c2f6e
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInput.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.compare;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.structuremergeviewer.DiffNode;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.Policy;
+
+public class SyncInfoCompareInput extends CompareEditorInput {
+
+ private SyncInfo sync;
+ private SyncInfoDiffNode node;
+
+ /* protected */ SyncInfoCompareInput() {
+ super(new CompareConfiguration());
+ }
+
+ public SyncInfoCompareInput(SyncInfo sync) {
+ super(new CompareConfiguration());
+ this.sync = sync;
+
+ ITypedElement elements[] = SyncInfoDiffNode.getTypedElements(sync);
+ this.node = new SyncInfoDiffNode(elements[0], elements[1], elements[2], sync.getKind());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#prepareInput(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ // update the title now that the remote revision number as been fetched from the server
+ setTitle(getTitle());
+ updateLabels();
+ return node;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#getTitle()
+ */
+ public String getTitle() {
+ return node.getName();
+ }
+
+ protected void updateLabels() {
+ CompareConfiguration config = getCompareConfiguration();
+ config.setLeftLabel(Policy.bind("SyncInfoCompareInput.localLabel"));
+ config.setRightLabel(Policy.bind("SyncInfoCompareInput.remoteLabel"));
+ config.setAncestorLabel(Policy.bind("SyncInfoCompareInput.baseLabel"));
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object other) {
+ if(other == this) return true;
+ if(other instanceof SyncInfoCompareInput) {
+ return node.equals(((SyncInfoCompareInput)other).getCompareResult());
+ } else if(other instanceof SyncInfoCompareInputFinder) {
+ return true;
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see CompareEditorInput#saveChanges(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void saveChanges(IProgressMonitor pm) throws CoreException {
+ super.saveChanges(pm);
+ if (node instanceof DiffNode) {
+ try {
+ commit(pm, (DiffNode) node);
+ } finally {
+ setDirty(false);
+ }
+ }
+ }
+
+ /*
+ * Recursively walks the diff tree and commits all changes.
+ */
+ private static void commit(IProgressMonitor pm, DiffNode node) throws CoreException {
+ ITypedElement left= node.getLeft();
+ if (left instanceof LocalResourceTypedElement)
+ ((LocalResourceTypedElement) left).commit(pm);
+
+ ITypedElement right= node.getRight();
+ if (right instanceof LocalResourceTypedElement)
+ ((LocalResourceTypedElement) right).commit(pm);
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInputFinder.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInputFinder.java
new file mode 100644
index 000000000..5f88d1eb3
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoCompareInputFinder.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.compare;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IPersistableElement;
+
+
+/**
+ * SyncInfoCompareInputFinder
+ */
+public class SyncInfoCompareInputFinder implements IEditorInput {
+
+ public boolean equals(Object other) {
+ if(other instanceof SyncInfoCompareInput) {
+ return true;
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IEditorInput#exists()
+ */
+ public boolean exists() {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IEditorInput#getImageDescriptor()
+ */
+ public ImageDescriptor getImageDescriptor() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IEditorInput#getName()
+ */
+ public String getName() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IEditorInput#getPersistable()
+ */
+ public IPersistableElement getPersistable() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IEditorInput#getToolTipText()
+ */
+ public String getToolTipText() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoDiffNode.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoDiffNode.java
new file mode 100644
index 000000000..950754d65
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/compare/SyncInfoDiffNode.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.compare;
+
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.structuremergeviewer.DiffNode;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.sync.IRemoteResource;
+
+public class SyncInfoDiffNode extends DiffNode {
+
+ /**
+ * Creates a new file node.
+ */
+ public SyncInfoDiffNode(ITypedElement base, ITypedElement local, ITypedElement remote, int syncKind) {
+ super(syncKind, base, local, remote);
+ }
+
+ /**
+ * Returns the typed element for this sync element. The returned elements
+ * are [0] base, [1] local, [2] remote.
+ */
+ static public ITypedElement[] getTypedElements(final SyncInfo sync) {
+ IRemoteResource baseResource = sync.getBase();
+ IRemoteResource remoteResource = sync.getRemote();
+ IResource localResource = sync.getLocal();
+
+ ITypedElement te[] = new ITypedElement[3];
+
+ if(baseResource != null) {
+ te[0] = new RemoteResourceTypedElement(baseResource);
+ }
+ if(remoteResource != null) {
+ te[2] = new RemoteResourceTypedElement(remoteResource);
+ }
+
+ if(localResource != null && localResource.exists()) {
+ te[1] = new LocalResourceTypedElement(localResource) {
+ public boolean isEditable() {
+ int kind = sync.getKind();
+ if(SyncInfo.getDirection(kind) == SyncInfo.OUTGOING && SyncInfo.getChange(kind) == SyncInfo.DELETION) {
+ return false;
+ }
+ return super.isEditable();
+ }
+ };
+ }
+ return te;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ChangeFiltersContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ChangeFiltersContentProvider.java
new file mode 100644
index 000000000..3723dbd0d
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ChangeFiltersContentProvider.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.team.internal.ui.sync.actions.SyncViewerChangeFilters;
+
+/**
+ * This is a content provider for the SyncViewerChangeFilters
+ */
+public class ChangeFiltersContentProvider implements IStructuredContentProvider {
+
+ private SyncViewerChangeFilters filters;
+
+ /**
+ * @param filters
+ */
+ public ChangeFiltersContentProvider(SyncViewerChangeFilters filters) {
+ this.filters = filters;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+ */
+ public Object[] getElements(Object inputElement) {
+ return filters.getFilters();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IContentProvider#dispose()
+ */
+ public void dispose() {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
+ */
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ /**
+ * @return
+ */
+ public Object[] getInitialSelections() {
+ return filters.getActiveFilters();
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ISyncSetChangedListener.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ISyncSetChangedListener.java
new file mode 100644
index 000000000..c1a8ceac6
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/ISyncSetChangedListener.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+/**
+ * This interface is used to receive SyncSetChangedEvents from a sync set.
+ */
+public interface ISyncSetChangedListener {
+
+ /**
+ * The sync set has changed.
+ * @param event
+ */
+ public void syncSetChanged(SyncSetChangedEvent event);
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SubscriberInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SubscriberInput.java
new file mode 100644
index 000000000..a3198d41b
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SubscriberInput.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.internal.ui.Policy;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+import org.eclipse.ui.IWorkingSet;
+
+/**
+ * SubscriberInput encapsulates the UI model for synchronization changes associated
+ * with a TeamSubscriber.
+ */
+public class SubscriberInput {
+
+ /*
+ * The subscriberInput manages a sync set that contains all of the out-of-sync elements
+ * of a subscriber.
+ */
+ private SyncSetInputFromSubscriberWorkingSet subscriberInput;
+
+ /*
+ * The filteredInput manages a sync set that contains a filtered list of the out-of-sync
+ * elements from another sync set. This is an optimization to allow filters to be applied
+ * to the subscriber input and is the input for a UI model.
+ */
+ private SyncSetInputFromSyncSet filteredInput;
+
+ /*
+ * The subscriber
+ */
+ private TeamSubscriber subscriber;
+
+ SubscriberInput(TeamSubscriber subscriber) {
+ this.subscriber = subscriber;
+ subscriberInput = new SyncSetInputFromSubscriberWorkingSet();
+ filteredInput = new SyncSetInputFromSyncSet();
+ }
+
+ /*
+ * Initializes this input with the contents of the subscriber and installs the given set of filters. This
+ * is a long running operation.
+ */
+ public void prepareInput(IProgressMonitor monitor) throws TeamException {
+ monitor.beginTask(null, 100);
+ try {
+ subscriberInput.setSubscriber(getSubscriber(), Policy.subMonitorFor(monitor, 70));
+ filteredInput.setInputSyncSet(subscriberInput.getSyncSet(), Policy.subMonitorFor(monitor, 30));
+ } finally {
+ monitor.done();
+ }
+ }
+
+ public TeamSubscriber getSubscriber() {
+ return subscriber;
+ }
+
+ public SyncSet getSyncSet() {
+ return filteredInput.getSyncSet();
+ }
+
+ public void setFilter(SyncInfoFilter filter, IProgressMonitor monitor) throws TeamException {
+ filteredInput.setFilter(filter, monitor);
+ }
+
+ public void dispose() {
+ subscriberInput.disconnect();
+ filteredInput.disconnect();
+ }
+
+ public IWorkingSet getWorkingSet() {
+ return subscriberInput.getWorkingSet();
+ }
+
+ public void setWorkingSet(IWorkingSet set) {
+ subscriberInput.setWorkingSet(set);
+ }
+
+ public IResource[] roots() throws TeamException {
+ return subscriberInput.getRoots();
+ }
+
+ public SyncSet getSubscriberInputSyncSet() {
+ return subscriberInput.getSyncSet();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncResource.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncResource.java
new file mode 100644
index 000000000..f4e3378be
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncResource.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * This is the UI model object representing a SyncInfo for a resource.
+ * The main purpose of this class is to allow menu object contributions
+ * to be applied to these resources.
+ */
+public class SyncResource implements IAdaptable {
+
+ private SyncSet syncSet;
+ private IResource resource;
+
+ /**
+ * @param info
+ */
+ public SyncResource(SyncSet syncSet, IResource resource) {
+ this.syncSet = syncSet;
+ this.resource = resource;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ public Object getAdapter(Class adapter) {
+ if (adapter == IResource.class) {
+ return getLocalResource();
+ } else if (adapter == SyncInfo.class) {
+ return getSyncInfo();
+ }
+ return null;
+ }
+
+ /**
+ * @return
+ */
+ public IResource getLocalResource() {
+ return resource;
+ }
+
+ /**
+ * @return
+ */
+ public SyncInfo getSyncInfo() {
+ return syncSet.getSyncInfo(resource);
+ }
+
+ /**
+ * Return an array of all descendants (including the receiver) that have
+ * a non-null sync-info.
+ * @return
+ */
+ public SyncResource[] getOutOfSyncDescendants() {
+ List result = new ArrayList();
+ SyncInfo info = getSyncInfo();
+ if (info != null) {
+ result.add(this);
+ }
+ Object[] members = SyncSet.members(syncSet, getLocalResource());
+ for (int i = 0; i < members.length; i++) {
+ Object object = members[i];
+ if (object instanceof SyncResource) {
+ SyncResource child = (SyncResource) object;
+ result.addAll(Arrays.asList(child.getOutOfSyncDescendants()));
+ }
+ }
+ return (SyncResource[]) result.toArray(new SyncResource[result.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object object) {
+ if (object instanceof SyncResource) {
+ SyncResource syncResource = (SyncResource) object;
+ return getLocalResource().equals(syncResource.getLocalResource());
+ }
+ return super.equals(object);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return getLocalResource().hashCode();
+ }
+
+ /**
+ * @return
+ */
+ public int getChangeType() {
+ return getKind() & SyncInfo.CHANGE_MASK;
+ }
+
+ /**
+ * @return
+ */
+ public int getChangeDirection() {
+ return getKind() & SyncInfo.DIRECTION_MASK;
+ }
+
+ /**
+ * @return
+ */
+ public int getKind() {
+ SyncInfo info = getSyncInfo();
+ if (info == null) return 0;
+ return info.getKind();
+ }
+
+ /**
+ * @return
+ */
+ public SyncResource getParent() {
+ Object parent = SyncSet.getParent(syncSet, this);
+ if (parent instanceof SyncResource) {
+ return (SyncResource)parent;
+ }
+ return null;
+ }
+
+ /**
+ * @return
+ */
+ public IResource getResource() {
+ return resource;
+ }
+
+ public String toString() {
+ return "Sync for " + getResource().getFullPath().toString();
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSet.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSet.java
new file mode 100644
index 000000000..035c7f60e
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSet.java
@@ -0,0 +1,390 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.TeamUIPlugin;
+
+/**
+ * This class keeps track of a set of resources that are associated with
+ * a sychronization view/operation.
+ */
+public class SyncSet {
+ // fields used to hold resources of interest
+ // {IPath -> SyncInfo}
+ protected Map resources = new HashMap();
+
+ // {IPath -> Set of deep out of sync child IResources}
+ // weird thing is that the child set will include the
+ // parent if the parent is out of sync
+ protected Map parents = new HashMap();
+
+ // fields used for change notification
+ protected SyncSetChangedEvent changes;
+ protected Set listeners = new HashSet();
+
+ // {int sync kind -> int number of infos with that sync kind in this sync set}
+ protected Map stats = new HashMap();
+
+ public SyncSet() {
+ resetChanges();
+ }
+
+ /**
+ * Return the IResource for the given model object that was returned by
+ * SyncSet#members(IResource). Return <code>null</code> if the given
+ * object does not have a corresponding IResource.
+ *
+ * @param element
+ * @return
+ */
+ public static IResource getIResource(Object element) {
+ IResource resource = null;
+ if (element instanceof IResource) {
+ return (IResource)element;
+ } if (element instanceof SyncInfo) {
+ resource = ((SyncInfo) element).getLocal();
+ } else if (element instanceof SyncResource) {
+ resource = ((SyncResource)element).getLocalResource();
+ }
+ return resource;
+ }
+
+ /**
+ * Return the sync kind for the given model object that was returned by
+ * SyncSet#members(IResource). If syncSet is null, then the
+ * sync kind for SyncContainers will always be 0.
+ *
+ * @param element
+ * @return
+ */
+ public static int getSyncKind(SyncSet syncSet, Object element) {
+ SyncInfo info = getSyncInfo(syncSet, element);
+ if (info != null) {
+ return info.getKind();
+ }
+ return 0;
+ }
+
+ public static Object[] members(SyncSet syncSet, IResource resource) {
+ return syncSet.members(resource);
+ }
+
+ /**
+ * Return the SyncInfo for the given model object that was returned by
+ * SyncSet#members(IResource). If syncSet is null, then the
+ * sync info will also be null.
+ *
+ * @param element
+ * @return
+ */
+ public static SyncInfo getSyncInfo(SyncSet syncSet, Object element) {
+ if (element instanceof SyncInfo) {
+ return ((SyncInfo) element);
+ } else if (element instanceof SyncResource) {
+ SyncResource syncResource = (SyncResource)element;
+ return syncResource.getSyncInfo();
+ }
+ return null;
+ }
+
+ public static SyncInfo getSyncInfo(Object element) {
+ if (element instanceof SyncInfo) {
+ return ((SyncInfo) element);
+ } else if (element instanceof SyncResource) {
+ SyncResource syncResource = (SyncResource)element;
+ return syncResource.getSyncInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Get the model object (SyncSet, SyncInfo or SyncContainer) that is the
+ * parent of the given model object.
+ *
+ * @param syncSet
+ * @param object
+ * @return
+ */
+ public static Object getParent(SyncSet syncSet, Object object) {
+ IResource resource = getIResource(object);
+ if (resource == null) return null;
+ IContainer parent = resource.getParent();
+ return getModelObject(syncSet, parent);
+ }
+
+
+ /**
+ * Return the model object for the given IResource.
+ * @param resource
+ */
+ public static Object getModelObject(SyncSet syncSet, IResource resource) {
+ if (resource.getType() == IResource.ROOT) {
+ // TODO: A subscriber may not be rooted at the project!!!
+ return syncSet;
+ } else {
+ return new SyncResource(syncSet, resource);
+ }
+ }
+
+ protected void resetChanges() {
+ changes = new SyncSetChangedEvent(this);
+ stats.clear();
+ }
+
+ protected void fireChanges() {
+ // Use a synchronized block to ensure that the event we send is static
+ SyncSetChangedEvent event;
+ synchronized(this) {
+ event = changes;
+ resetChanges();
+ }
+ // Fire the events
+ for (Iterator iter = listeners.iterator(); iter.hasNext();) {
+ ISyncSetChangedListener listener = (ISyncSetChangedListener) iter.next();
+ listener.syncSetChanged(event);
+ }
+ }
+
+ /**
+ * Add a change listener
+ * @param provider
+ */
+ public void addSyncSetChangedListener(ISyncSetChangedListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a change listener
+ * @param provider
+ */
+ public void removeSyncSetChangedListener(ISyncSetChangedListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void add(SyncInfo info) {
+ internalAddSyncInfo(info);
+ changes.added(info);
+ IResource local = info.getLocal();
+ addToParents(local, local);
+ }
+
+ private void internalAddSyncInfo(SyncInfo info) {
+ IResource local = info.getLocal();
+ IPath path = local.getFullPath();
+ resources.put(path, info);
+ }
+
+ public int getCount(int directionFlag) {
+ Integer count = (Integer)stats.get(new Integer(directionFlag));
+ if(count == null) {
+ return 0;
+ }
+ return count.intValue();
+ }
+
+ protected void remove(IResource local) {
+ IPath path = local.getFullPath();
+ resources.remove(path);
+ changes.removed(local);
+ removeFromParents(local, local);
+ }
+
+ protected void changed(SyncInfo info) {
+ internalAddSyncInfo(info);
+ changes.changed(info);
+ }
+
+ /**
+ * Reset the sync set so it is empty
+ */
+ public void reset() {
+ resources.clear();
+ stats.clear();
+ parents.clear();
+ changes.reset();
+ }
+
+ protected boolean addToParents(IResource resource, IResource parent) {
+ if (parent.getType() == IResource.ROOT) {
+ return false;
+ }
+ // this flag is used to indicate if the parent was previosuly in the set
+ boolean addedParent = false;
+ if (parent.getType() == IResource.FILE) {
+ // the file is new
+ addedParent = true;
+ } else {
+ Set children = (Set)parents.get(parent.getFullPath());
+ if (children == null) {
+ children = new HashSet();
+ parents.put(parent.getFullPath(), children);
+ // this is a new folder in the sync set
+ addedParent = true;
+ }
+ children.add(resource);
+ }
+ // if the parent already existed and the resource is new, record it
+ if (!addToParents(resource, parent.getParent()) && addedParent) {
+ changes.addedRoot(parent);
+ }
+ return addedParent;
+ }
+
+ protected boolean removeFromParents(IResource resource, IResource parent) {
+ if (parent.getType() == IResource.ROOT) {
+ return false;
+ }
+ // this flag is used to indicate if the parent was removed from the set
+ boolean removedParent = false;
+ if (parent.getType() == IResource.FILE) {
+ // the file will be removed
+ removedParent = true;
+ } else {
+ Set children = (Set)parents.get(parent.getFullPath());
+ if (children != null) {
+ children.remove(resource);
+ if (children.isEmpty()) {
+ parents.remove(parent.getFullPath());
+ removedParent = true;
+ }
+ }
+ }
+ // if the parent wasn't removed and the resource was, record it
+ if (!removeFromParents(resource, parent.getParent()) && removedParent) {
+ changes.removedRoot(parent);
+ }
+ return removedParent;
+ }
+
+ /**
+ * Return the children of the given container who are either out-of-sync or contain
+ * out-of-sync resources.
+ *
+ * TODO: How does an IWorkbecnhAdapter fit into this picture (i.e. should
+ * the adapter be converting SyncInfo to SyncResource
+ *
+ * @param container
+ * @return
+ */
+ public Object[] members(IResource resource) {
+ if (resource.getType() == IResource.FILE) return new Object[0];
+ IContainer parent = (IContainer)resource;
+ if (parent.getType() == IResource.ROOT) return getRoots(parent);
+ // TODO: must be optimized so that we don't traverse all the deep children to find
+ // the immediate ones.
+ Set children = new HashSet();
+ IPath path = parent.getFullPath();
+ Set possibleChildren = (Set)parents.get(path);
+ if(possibleChildren != null) {
+ for (Iterator it = possibleChildren.iterator(); it.hasNext();) {
+ Object next = it.next();
+ IResource element = (IResource)next;
+ IPath childPath = element.getFullPath();
+ Object modelObject = null;
+ if(childPath.segmentCount() == (path.segmentCount() + 1)) {
+ modelObject = getModelObject(this, element);
+
+ } else if (childPath.segmentCount() > path.segmentCount()) {
+ IContainer childFolder = parent.getFolder(new Path(childPath.segment(path.segmentCount())));
+ modelObject = getModelObject(this, childFolder);
+ }
+ if (modelObject != null) {
+ children.add(modelObject);
+ }
+ }
+ }
+ return (Object[]) children.toArray(new Object[children.size()]);
+ }
+
+ /**
+ * @return
+ */
+ private Object[] getRoots(IContainer root) {
+ Set possibleChildren = parents.keySet();
+ Set children = new HashSet();
+ for (Iterator it = possibleChildren.iterator(); it.hasNext();) {
+ Object next = it.next();
+ IResource element = ((IWorkspaceRoot)root).findMember((IPath)next);
+ if (element != null) {
+ children.add(getModelObject(this, element.getProject()));
+ }
+ }
+ return (Object[]) children.toArray(new Object[children.size()]);
+ }
+
+ protected boolean hasMembers(IContainer container) {
+ return parents.containsKey(container.getFullPath());
+ }
+
+ /**
+ * Return an array of all the resources that are known to be out-of-sync
+ * @return
+ */
+ public SyncInfo[] allMembers() {
+ return (SyncInfo[]) resources.values().toArray(new SyncInfo[resources.size()]);
+ }
+
+ /**
+ * @param e
+ */
+ protected void log(TeamException e) {
+ // TODO: log or throw
+ TeamUIPlugin.log(e);
+ }
+
+ protected void removeAllChildren(IResource resource) {
+ // The parent map contains a set of all out-of-sync children
+ Set allChildren = (Set)parents.get(resource.getFullPath());
+ if (allChildren == null) return;
+ removeAll(allChildren);
+ }
+
+ protected void removeAll(Set allChildren) {
+ IResource [] removed = (IResource[]) allChildren.toArray(new IResource[allChildren.size()]);
+ for (int i = 0; i < removed.length; i++) {
+ remove(removed[i]);
+ }
+ }
+
+ protected SyncInfo getSyncInfo(IResource resource) {
+ return (SyncInfo)resources.get(resource.getFullPath());
+ }
+
+ /**
+ * This method is invoked by a SyncSetInput provider when the
+ * provider is starting to provide new input to the SyncSet
+ */
+ /* package */ void beginInput() {
+ resetChanges();
+ }
+
+ /**
+ * This method is invoked by a SyncSetInput provider when the
+ * provider is done providing new input to the SyncSet
+ */
+ /* package */ void endInput() {
+ fireChanges();
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetChangedEvent.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetChangedEvent.java
new file mode 100644
index 000000000..e199c0017
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetChangedEvent.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * This event keeps track of the changes in a sync set
+ */
+public class SyncSetChangedEvent {
+
+ private SyncSet set;
+
+ // List that accumulate changes
+ // SyncInfo
+ private Set changedResources = new HashSet();
+ private Set removedResources = new HashSet();
+ private Set addedResources = new HashSet();
+
+ // IResources
+ private Set removedRoots = new HashSet();
+ private Set addedRoots = new HashSet();
+
+ private boolean reset = false;
+
+ /**
+ *
+ */
+ public SyncSetChangedEvent(SyncSet set) {
+ super();
+ this.set = set;
+ }
+
+ /* package */ void added(SyncInfo info) {
+ addedResources.add(info);
+ }
+
+ /* package */ void removed(IResource resource) {
+ removedResources.add(resource);
+ }
+
+ /* package */ void changed(SyncInfo info) {
+ changedResources.add(info);
+ }
+
+ /**
+ * @param parent
+ */
+ public void removedRoot(IResource root) {
+ if (addedRoots.contains(root)) {
+ // The root was added and removed which is a no-op
+ addedRoots.remove(root);
+ } else {
+ // check if the root is a child of an existing root
+ // (in which case it need not be added).
+ // Also, remove any exisiting roots that are children
+ // of the new root
+ for (Iterator iter = removedRoots.iterator(); iter.hasNext();) {
+ IResource element = (IResource) iter.next();
+ // check if the root is already in the list
+ if (root.equals(element)) return;
+ if (isParent(root, element)) {
+ // the root invalidates the current element
+ iter.remove();
+ } else if (isParent(element, root)) {
+ // the root is a child of an existing element
+ return;
+ }
+ }
+ removedRoots.add(root);
+ }
+ }
+
+ private boolean isParent(IResource root, IResource element) {
+ return root.getFullPath().isPrefixOf(element.getFullPath());
+ }
+
+ /**
+ * @param parent
+ */
+ public void addedRoot(IResource parent) {
+ if (removedRoots.contains(parent)) {
+ // The root was re-added which is a no-op
+ removedRoots.remove(parent);
+ } else {
+ // TODO: May be added underneath another added root
+ addedRoots.add(parent);
+ }
+
+ }
+ /**
+ * @return
+ */
+ public SyncInfo[] getAddedResources() {
+ return (SyncInfo[]) addedResources.toArray(new SyncInfo[addedResources.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public IResource[] getAddedRoots() {
+ return (IResource[]) addedRoots.toArray(new IResource[addedRoots.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public SyncInfo[] getChangedResources() {
+ return (SyncInfo[]) changedResources.toArray(new SyncInfo[changedResources.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public IResource[] getRemovedResources() {
+ return (IResource[]) removedResources.toArray(new IResource[removedResources.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public IResource[] getRemovedRoots() {
+ return (IResource[]) removedRoots.toArray(new IResource[removedRoots.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public SyncSet getSet() {
+ return set;
+ }
+
+ /**
+ *
+ */
+ public void reset() {
+ reset = true;
+ }
+
+ public boolean isReset() {
+ return reset;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetContentProvider.java
new file mode 100644
index 000000000..5df4e7fd5
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetContentProvider.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.StructuredViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * This class provides the contents for a StructuredViewer using a SyncSet as the model
+ */
+public abstract class SyncSetContentProvider implements IStructuredContentProvider, ISyncSetChangedListener {
+
+ protected Viewer viewer;
+
+ protected SyncSet getSyncSet() {
+ Object input = viewer.getInput();
+ if (input == null) return null;
+ return (SyncSet)input;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
+ */
+ public void inputChanged(Viewer v, Object oldInput, Object newInput) {
+ this.viewer = v;
+ SyncSet oldSyncSet = null;
+ SyncSet newSyncSet = null;
+ if (oldInput instanceof SyncSet) {
+ oldSyncSet = (SyncSet) oldInput;
+ }
+ if (newInput instanceof SyncSet) {
+ newSyncSet = (SyncSet) newInput;
+ }
+ if (oldSyncSet != newSyncSet) {
+ if (oldSyncSet != null) {
+ oldSyncSet.removeSyncSetChangedListener(this);
+ }
+ if (newSyncSet != null) {
+ newSyncSet.addSyncSetChangedListener(this);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+ */
+ public abstract Object[] getElements(Object inputElement);
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IContentProvider#dispose()
+ */
+ public void dispose() {
+ SyncSet syncSet = getSyncSet();
+ if (syncSet != null) {
+ syncSet.removeSyncSetChangedListener(this);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.ISyncSetChangedListener#syncSetChanged(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ public void syncSetChanged(final SyncSetChangedEvent event) {
+ Control ctrl = viewer.getControl();
+ if (ctrl != null && !ctrl.isDisposed()) {
+ ctrl.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ handleSyncSetChanges(event);
+ }
+ });
+ }
+ }
+
+ /**
+ * Update the viewer with the sync-set changes, aditions and removals
+ * in the given event. This method is invoked from within the UI thread.
+ * @param event
+ */
+ protected void handleSyncSetChanges(SyncSetChangedEvent event) {
+ viewer.getControl().setRedraw(false);
+ if (event.isReset()) {
+ // On a reset, refresh the entire view
+ ((StructuredViewer) viewer).refresh();
+ } else {
+ handleResourceChanges(event);
+ handleResourceRemovals(event);
+ handleResourceAdditions(event);
+ }
+ viewer.getControl().setRedraw(true);
+ }
+
+ /**
+ * Update the viewer for the sync set changes in the provided event.
+ * This method is invoked by <code>handleSyncSetChanges</code>.
+ * Subclasses may override.
+ * @param event
+ * @see #handleSyncSetChanges(SyncSetChangedEvent)
+ */
+ protected void handleResourceChanges(SyncSetChangedEvent event) {
+ // Refresh the viewer for each changed resource
+ SyncInfo[] infos = event.getChangedResources();
+ for (int i = 0; i < infos.length; i++) {
+ ((StructuredViewer) viewer).refresh(SyncSet.getModelObject(getSyncSet(), infos[i].getLocal()), true);
+ }
+ }
+
+ /**
+ * Update the viewer for the sync set removals in the provided event.
+ * This method is invoked by <code>handleSyncSetChanges</code>.
+ * Subclasses may override.
+ * @param event
+ */
+ protected void handleResourceRemovals(SyncSetChangedEvent event) {
+ // Update the viewer for each removed resource
+ IResource[] removed = event.getRemovedRoots();
+ for (int i = 0; i < removed.length; i++) {
+ IResource resource = removed[i];
+ ((StructuredViewer) viewer).refresh(SyncSet.getModelObject(getSyncSet(), resource));
+ }
+ }
+
+ /**
+ * Update the viewer for the sync set additions in the provided event.
+ * This method is invoked by <code>handleSyncSetChanges</code>.
+ * Subclasses may override.
+ * @param event
+ */
+ protected void handleResourceAdditions(SyncSetChangedEvent event) {
+ // Update the viewer for each of the added resource's parents
+ IResource[] added = event.getAddedRoots();
+ for (int i = 0; i < added.length; i++) {
+ IResource resource = added[i];
+ ((StructuredViewer) viewer).refresh(SyncSet.getModelObject(getSyncSet(), resource.getParent()));
+ }
+ }
+
+ public StructuredViewer getViewer() {
+ return (StructuredViewer)viewer;
+ }
+
+ protected Object getModelObject(IResource resource) {
+ return SyncSet.getModelObject(getSyncSet(), resource);
+ }
+
+ protected Object getModelObject(SyncInfo info) {
+ return getModelObject(info.getLocal());
+ }
+
+ protected Object[] getModelObjects(SyncInfo[] infos) {
+ Object[] resources = new Object[infos.length];
+ for (int i = 0; i < resources.length; i++) {
+ resources[i] = getModelObject(infos[i]);
+ }
+ return resources;
+ }
+
+ protected Object[] getModelObjects(IResource[] resources) {
+ Object[] result = new Object[resources.length];
+ for (int i = 0; i < resources.length; i++) {
+ result[i] = getModelObject(resources[i]);
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInput.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInput.java
new file mode 100644
index 000000000..b593aecbc
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInput.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.ui.sync.SyncInfoFilter;
+
+/**
+ * This is the superclass for all SyncSet input providers
+ */
+public abstract class SyncSetInput {
+
+ private SyncSet syncSet = new SyncSet();
+ private SyncInfoFilter filter = new SyncInfoFilter();
+
+ public SyncSet getSyncSet() {
+ return syncSet;
+ }
+
+ protected void log(TeamException e) {
+ // TODO: log or throw or communicate to the view that an error has occured
+ }
+
+ /**
+ * The input is no longer being used. Disconnect it from its source.
+ */
+ public abstract void disconnect();
+
+ /**
+ * Reset the input. This will clear the current contents of the sync set and
+ * obtain the contents from the input source.
+ *
+ * @param monitor
+ * @throws TeamException
+ */
+ protected void reset(IProgressMonitor monitor) throws TeamException {
+ try {
+ syncSet.beginInput();
+ syncSet.reset();
+ fetchInput(monitor);
+ } finally {
+ getSyncSet().endInput();
+ }
+ }
+
+ /**
+ * This method is invoked from reset to get all the sync information from
+ * the input source.
+ *
+ * @param monitor
+ */
+ protected abstract void fetchInput(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Collect the change in the provided sync info.
+ *
+ * @param info
+ */
+ protected void collect(SyncInfo info) {
+ boolean isOutOfSync = filter.select(info);
+ SyncInfo oldInfo = syncSet.getSyncInfo(info.getLocal());
+ boolean wasOutOfSync = oldInfo != null;
+ if (isOutOfSync) {
+ if (wasOutOfSync) {
+ syncSet.changed(info);
+ } else {
+ syncSet.add(info);
+ }
+ } else if (wasOutOfSync) {
+ syncSet.remove(info.getLocal());
+ }
+ }
+
+ protected void remove(IResource resource) {
+ SyncInfo oldInfo = syncSet.getSyncInfo(resource);
+ boolean wasOutOfSync = oldInfo != null;
+ if (oldInfo != null) {
+ syncSet.remove(resource);
+ }
+ }
+
+ /**
+ * @return
+ */
+ public SyncInfoFilter getFilter() {
+ return filter;
+ }
+
+ /**
+ * @param filter
+ */
+ public void setFilter(SyncInfoFilter filter, IProgressMonitor monitor) throws TeamException {
+ this.filter = filter;
+ reset(monitor);
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSubscriberWorkingSet.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSubscriberWorkingSet.java
new file mode 100644
index 000000000..65cf0c5b3
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSubscriberWorkingSet.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.ui.TeamUIPlugin;
+import org.eclipse.ui.IWorkingSet;
+
+/**
+ * Thsi class filters the sync set input using a working set
+ */
+public class SyncSetInputFromSubscriberWorkingSet extends SyncSetInputFromSubscriber {
+
+ private IWorkingSet workingSet;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInputFromSubscriber#collect(org.eclipse.core.resources.IResource, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void collect(IResource resource, IProgressMonitor monitor) throws TeamException {
+ // Only collect the change for the resource if the resource is in the working set
+ if (isIncluded(resource)) {
+ super.collect(resource, monitor);
+ }
+ }
+
+ /*
+ * Answer true if the given resource is included in the working set
+ */
+ private boolean isIncluded(IResource resource) {
+ // if there's no set, the resource is included
+ if (workingSet == null) return true;
+ // otherwise, if their is a parent of the resource in the set,
+ // it is included
+ Object[] elements = workingSet.getElements();
+ List result = new ArrayList();
+ for (int i = 0; i < elements.length; i++) {
+ IResource setResource = getResource(elements[i]);
+ if (isParent(setResource, resource)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInputFromSubscriber#getRoots()
+ */
+ protected IResource[] getRoots() throws TeamException {
+ IResource[] roots = super.getRoots();
+ if (workingSet == null) return roots;
+
+ // filter the roots by the selected working set
+ Set result = new HashSet();
+ for (int i = 0; i < roots.length; i++) {
+ IResource resource = roots[i];
+ result.addAll(Arrays.asList(getIntersectionWithSet(resource)));
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ /*
+ * Answer the intersection between the given resource and it's children
+ * and the receiver's working set.
+ */
+ private IResource[] getIntersectionWithSet(IResource resource) {
+ Object[] elements = workingSet.getElements();
+ List result = new ArrayList();
+ for (int i = 0; i < elements.length; i++) {
+ IResource setResource = getResource(elements[i]);
+ if (setResource != null) {
+ if (isParent(resource, setResource)) {
+ try {
+ if (getSubscriber().isSupervised(setResource)) {
+ result.add(setResource);
+ }
+ } catch (TeamException e) {
+ // Log the exception and add the resource to the list
+ TeamUIPlugin.log(e);
+ result.add(setResource);
+ }
+ } else if (isParent(setResource, resource)) {
+ result.add(resource);
+ }
+ }
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ private boolean isParent(IResource parent, IResource child) {
+ return (parent.getFullPath().isPrefixOf(child.getFullPath()));
+ }
+
+ private IResource getResource(Object object) {
+ if (object instanceof IResource) {
+ return (IResource)object;
+ } else if (object instanceof IAdaptable) {
+ return (IResource)((IAdaptable)object).getAdapter(IResource.class);
+ }
+ return null;
+ }
+
+ public IWorkingSet getWorkingSet() {
+ return workingSet;
+ }
+
+ public void setWorkingSet(IWorkingSet set) {
+ this.workingSet = set;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSyncSet.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSyncSet.java
new file mode 100644
index 000000000..6c84ef99f
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetInputFromSyncSet.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * Ths class uses the contents of one sync set as the input of another.
+ */
+public class SyncSetInputFromSyncSet extends SyncSetInput implements ISyncSetChangedListener {
+
+ SyncSet inputSyncSet;
+
+ /**
+ * @param syncSet
+ */
+ protected SyncSetInputFromSyncSet() {
+ }
+
+ /**
+ * @return
+ */
+ public SyncSet getInputSyncSet() {
+ return inputSyncSet;
+ }
+
+ private void connect(SyncSet set) {
+ if (this.inputSyncSet != null) return;
+ this.inputSyncSet = set;
+ inputSyncSet.addSyncSetChangedListener(this);
+ }
+
+ public void disconnect() {
+ if (inputSyncSet == null) return;
+ inputSyncSet.removeSyncSetChangedListener(this);
+ inputSyncSet = null;
+ }
+
+ /**
+ * @param set
+ */
+ public void setInputSyncSet(SyncSet set, IProgressMonitor monitor) throws TeamException {
+ if (inputSyncSet != null) disconnect();
+ connect(set);
+ reset(monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.AbstractSyncSet#initialize(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void fetchInput(IProgressMonitor monitor) throws TeamException {
+ if (inputSyncSet == null) return;
+ SyncInfo[] infos = inputSyncSet.allMembers();
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i]);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.ISyncSetChangedListener#syncSetChanged(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ public void syncSetChanged(SyncSetChangedEvent event) {
+ SyncSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ if (event.isReset()) {
+ syncSet.reset();
+ }
+ syncSetChanged(event.getChangedResources());
+ syncSetChanged(event.getAddedResources());
+
+ remove(event.getRemovedResources());
+ } finally {
+ getSyncSet().endInput();
+ }
+ }
+
+ private void syncSetChanged(SyncInfo[] infos) {
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i]);
+ }
+ }
+
+ private void remove(IResource[] resources) {
+ for (int i = 0; i < resources.length; i++) {
+ remove(resources[i]);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTableContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTableContentProvider.java
new file mode 100644
index 000000000..da6e48209
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTableContentProvider.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * This class provides the contents for a TableViewer using a SyncSet as the model
+ */
+public class SyncSetTableContentProvider extends SyncSetContentProvider {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+ */
+ public Object[] getElements(Object element) {
+ SyncInfo[] infos = getSyncSet().allMembers();
+ return getModelObjects(infos);
+ }
+
+ public TableViewer getTableViewer() {
+ if (viewer instanceof TableViewer) {
+ return (TableViewer)viewer;
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetContentProvider#handleResourceAdditions(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ protected void handleResourceAdditions(SyncSetChangedEvent event) {
+ TableViewer table = getTableViewer();
+ if (table != null) {
+ SyncInfo[] infos = event.getAddedResources();
+ table.add(getModelObjects(infos));
+ } else {
+ super.handleResourceAdditions(event);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetContentProvider#handleResourceRemovals(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ protected void handleResourceRemovals(SyncSetChangedEvent event) {
+ TableViewer table = getTableViewer();
+ if (table != null) {
+ IResource[] resources = event.getRemovedResources();
+ table.remove(getModelObjects(resources));
+ } else {
+ super.handleResourceRemovals(event);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTreeContentProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTreeContentProvider.java
new file mode 100644
index 000000000..2d8c97318
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncSetTreeContentProvider.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jface.viewers.AbstractTreeViewer;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+
+/**
+ * This class provides the contents for a AbstractTreeViewer using a SyncSet as the model
+ */
+public class SyncSetTreeContentProvider extends SyncSetContentProvider implements ITreeContentProvider {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetContentProvider#getElements(java.lang.Object)
+ */
+ public Object[] getElements(Object inputElement) {
+ return getChildren(inputElement);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
+ */
+ public Object[] getChildren(Object element) {
+ IResource resource = SyncSet.getIResource(element);
+ if (resource != null) {
+ return SyncSet.members(getSyncSet(), resource);
+ } else if (element instanceof SyncSet) {
+ return SyncSet.members(getSyncSet(), ResourcesPlugin.getWorkspace().getRoot());
+ }
+ return new Object[0];
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
+ */
+ public boolean hasChildren(Object element) {
+ return getChildren(element).length > 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
+ */
+ public Object getParent(Object element) {
+ IResource resource = SyncSet.getIResource(element);
+ if (resource == null) return null;
+ IContainer parent = resource.getParent();
+ return SyncSet.getModelObject(getSyncSet(), parent);
+ }
+
+ public AbstractTreeViewer getTreeViewer() {
+ if (viewer instanceof AbstractTreeViewer) {
+ return (AbstractTreeViewer)viewer;
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetContentProvider#handleResourceAdditions(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ protected void handleResourceAdditions(SyncSetChangedEvent event) {
+ AbstractTreeViewer tree = getTreeViewer();
+ if (tree != null) {
+ IResource[] added = event.getAddedRoots();
+ // TODO: Should group added roots by their parent
+ for (int i = 0; i < added.length; i++) {
+ IResource resource = added[i];
+ Object parent;
+ if (resource.getType() == IResource.PROJECT) {
+ parent = getSyncSet();
+ } else {
+ parent = SyncSet.getModelObject(getSyncSet(), resource.getParent());
+ }
+ tree.add(parent, SyncSet.getModelObject(getSyncSet(), resource));
+ }
+ } else {
+ super.handleResourceAdditions(event);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetContentProvider#handleResourceRemovals(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ protected void handleResourceRemovals(SyncSetChangedEvent event) {
+ AbstractTreeViewer tree = getTreeViewer();
+ if (tree != null) {
+ IResource[] roots = event.getRemovedRoots();
+ if (roots.length == 0) return;
+ Object[] modelRoots = new Object[roots.length];
+ for (int i = 0; i < modelRoots.length; i++) {
+ modelRoots[i] = SyncSet.getModelObject(getSyncSet(), roots[i]);
+ }
+ tree.remove(modelRoots);
+ } else {
+ super.handleResourceRemovals(event);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerLabelProvider.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerLabelProvider.java
new file mode 100644
index 000000000..30134cc82
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerLabelProvider.java
@@ -0,0 +1,102 @@
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.DecoratingLabelProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.team.core.sync.IRemoteSyncElement;
+import org.eclipse.ui.internal.WorkbenchPlugin;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+
+/**
+ * The SyncViewerLabelProvider can be used in either a tree or table.
+ */
+public class SyncViewerLabelProvider extends LabelProvider implements ITableLabelProvider {
+
+ //column constants
+ private static final int COL_RESOURCE = 0;
+ private static final int COL_PARENT = 1;
+
+ // Keep track of the compare and workbench image providers
+ // so they can be properly disposed
+ CompareConfiguration compareConfig = new CompareConfiguration();
+ WorkbenchLabelProvider workbenchLabelProvider = new WorkbenchLabelProvider();
+
+ /**
+ * Returns a sync view label provider that is hooked up to the decorator
+ * mechanism.
+ *
+ * @return a new <code>DecoratingLabelProvider</code> which wraps a <code>
+ * new <code>WorkbenchLabelProvider</code>
+ */
+ public static ILabelProvider getDecoratingLabelProvider() {
+ return new DecoratingLabelProvider(
+ new SyncViewerLabelProvider(),
+ WorkbenchPlugin
+ .getDefault()
+ .getWorkbench()
+ .getDecoratorManager()
+ .getLabelDecorator());
+ }
+
+ public SyncViewerLabelProvider() {
+ }
+
+ public String getText(Object element) {
+ IResource resource = SyncSet.getIResource(element);
+ return workbenchLabelProvider.getText(resource);
+ }
+
+ public Image getImage(Object element) {
+ IResource resource = SyncSet.getIResource(element);
+ int kind= SyncSet.getSyncKind(null /* sync set */, element);
+ switch (kind & IRemoteSyncElement.DIRECTION_MASK) {
+ case IRemoteSyncElement.OUTGOING:
+ kind = (kind &~ IRemoteSyncElement.OUTGOING) | IRemoteSyncElement.INCOMING;
+ break;
+ case IRemoteSyncElement.INCOMING:
+ kind = (kind &~ IRemoteSyncElement.INCOMING) | IRemoteSyncElement.OUTGOING;
+ break;
+ }
+ Image image = workbenchLabelProvider.getImage(resource);
+ return compareConfig.getImage(image, kind);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
+ */
+ public void dispose() {
+ super.dispose();
+ workbenchLabelProvider.dispose();
+ compareConfig.dispose();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
+ */
+ public Image getColumnImage(Object element, int columnIndex) {
+ if (columnIndex == COL_RESOURCE) {
+ return getImage(element);
+ } else if (columnIndex == COL_PARENT) {
+ IResource resource = SyncSet.getIResource(element);
+ return null;
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
+ */
+ public String getColumnText(Object element, int columnIndex) {
+ if (columnIndex == COL_RESOURCE) {
+ return getText(element);
+ } else if (columnIndex == COL_PARENT) {
+ IResource resource = SyncSet.getIResource(element);
+ return resource.getParent().getFullPath().toString();
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerSorter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerSorter.java
new file mode 100644
index 000000000..a5680c34a
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerSorter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.ViewerSorter;
+
+/**
+ * This class sorts the model elements that appear in the SyncViewer
+ */
+public class SyncViewerSorter extends ViewerSorter {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ViewerSorter#category(java.lang.Object)
+ */
+ public int category(Object element) {
+ IResource resource = getResource(element);
+ if (resource != null) {
+ switch(resource.getType()) {
+ case IResource.PROJECT: return 1;
+ case IResource.FOLDER: return 2;
+ case IResource.FILE: return 3;
+ }
+ }
+ return super.category(element);
+ }
+
+ protected IResource getResource(Object element) {
+ return SyncSet.getIResource(element);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerTableSorter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerTableSorter.java
new file mode 100644
index 000000000..47666a4c8
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/sync/views/SyncViewerTableSorter.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.ui.sync.views;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.TableColumn;
+
+/**
+ * Provides support for sorting the table viewer of the SyncViewer by column
+ */
+public class SyncViewerTableSorter extends SyncViewerSorter {
+
+ private boolean reversed = false;
+ private int columnNumber;
+
+ //column constants
+ public static final int COL_NAME = 0;
+ public static final int COL_PARENT = 1;
+
+ // column headings: "Revision" "Tags" "Date" "Author" "Comment"
+ private int[][] SORT_ORDERS_BY_COLUMN = {
+ {COL_NAME, COL_PARENT}, /* name */
+ {COL_PARENT, COL_NAME} /* parent */
+ };
+
+ /**
+ * Return a listener that will change the sorter in the table when the column header
+ * is clicked.
+ */
+ public static SelectionListener getColumnListener(final TableViewer tableViewer) {
+ /**
+ * This class handles selections of the column headers.
+ * Selection of the column header will cause resorting
+ * of the shown tasks using that column's sorter.
+ * Repeated selection of the header will toggle
+ * sorting order (ascending versus descending).
+ */
+ return new SelectionAdapter() {
+ /**
+ * Handles the case of user selecting the
+ * header area.
+ * <p>If the column has not been selected previously,
+ * it will set the sorter of that column to be
+ * the current tasklist sorter. Repeated
+ * presses on the same column header will
+ * toggle sorting order (ascending/descending).
+ */
+ public void widgetSelected(SelectionEvent e) {
+ // column selected - need to sort
+ int column = tableViewer.getTable().indexOf((TableColumn) e.widget);
+ SyncViewerTableSorter oldSorter = (SyncViewerTableSorter)tableViewer.getSorter();
+ if (oldSorter != null && column == oldSorter.getColumnNumber()) {
+ oldSorter.setReversed(!oldSorter.isReversed());
+ tableViewer.refresh();
+ } else {
+ tableViewer.setSorter(new SyncViewerTableSorter(column));
+ }
+ }
+ };
+ }
+
+ /**
+ *
+ */
+ public SyncViewerTableSorter(int columnNumber) {
+ super();
+ this.columnNumber = columnNumber;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
+ */
+ public int compare(Viewer viewer, Object e1, Object e2) {
+
+ IResource resource1 = getResource(e1);
+ IResource resource2 = getResource(e2);
+ int result = 0;
+ if (resource1 == null || resource2 == null) {
+ result = super.compare(viewer, e1, e2);
+ } else {
+ int[] columnSortOrder = SORT_ORDERS_BY_COLUMN[columnNumber];
+ for (int i = 0; i < columnSortOrder.length; ++i) {
+ result = compareColumnValue(columnSortOrder[i], resource1, resource2);
+ if (result != 0)
+ break;
+ }
+ }
+ if (reversed)
+ result = -result;
+ return result;
+ }
+
+ /**
+ * Compares two resources, based only on the value of the specified column.
+ */
+ int compareColumnValue(int columnNumber, IResource e1, IResource e2) {
+ switch (columnNumber) {
+ case COL_NAME: /* revision */
+
+ // Category behavior from superclass
+ int cat1 = category(e1);
+ int cat2 = category(e2);
+
+ if (cat1 != cat2)
+ return cat1 - cat2;
+
+ // cat1 == cat2
+
+ return getCollator().compare(e1.getName(), e2.getName());
+ case COL_PARENT: /* parent */
+ return getCollator().compare(e1.getParent().getFullPath().toString(), e2.getParent().getFullPath().toString());
+ default:
+ return 0;
+ }
+ }
+ /**
+ * Returns the number of the column by which this is sorting.
+ */
+ public int getColumnNumber() {
+ return columnNumber;
+ }
+ /**
+ * Returns true for descending, or false
+ * for ascending sorting order.
+ */
+ public boolean isReversed() {
+ return reversed;
+ }
+ /**
+ * Sets the sorting order.
+ */
+ public void setReversed(boolean newReversed) {
+ reversed = newReversed;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AndSyncInfoFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AndSyncInfoFilter.java
new file mode 100644
index 000000000..c815dc6f1
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AndSyncInfoFilter.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * Selects SyncInfo which match all child filters
+ */
+public class AndSyncInfoFilter extends CompoundSyncInfoFilter {
+ public AndSyncInfoFilter(SyncInfoFilter[] filters) {
+ super(filters);
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetFilter#select(org.eclipse.team.core.sync.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ for (int i = 0; i < filters.length; i++) {
+ SyncInfoFilter filter = filters[i];
+ if (!filter.select(info)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AutomergableFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AutomergableFilter.java
new file mode 100644
index 000000000..af4f9296e
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/AutomergableFilter.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * Selects SyncInfo that are automergable
+ */
+public class AutomergableFilter extends SyncInfoFilter {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetFilter#select(org.eclipse.team.core.sync.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ return (info.getKind() & SyncInfo.AUTOMERGE_CONFLICT) != 0;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/CompoundSyncInfoFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/CompoundSyncInfoFilter.java
new file mode 100644
index 000000000..6c7929772
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/CompoundSyncInfoFilter.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+public abstract class CompoundSyncInfoFilter extends SyncInfoFilter {
+ protected SyncInfoFilter[] filters;
+ public CompoundSyncInfoFilter(SyncInfoFilter[] filters) {
+ this.filters = filters;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/OrSyncInfoFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/OrSyncInfoFilter.java
new file mode 100644
index 000000000..b6424afea
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/OrSyncInfoFilter.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * Selects SyncInfo that match any of the child filters.
+ */
+public class OrSyncInfoFilter extends CompoundSyncInfoFilter {
+ public OrSyncInfoFilter(SyncInfoFilter[] filters) {
+ super(filters);
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetFilter#select(org.eclipse.team.core.sync.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ for (int i = 0; i < filters.length; i++) {
+ SyncInfoFilter filter = filters[i];
+ if (filter.select(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/PseudoConflictFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/PseudoConflictFilter.java
new file mode 100644
index 000000000..a8a9068c1
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/PseudoConflictFilter.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * This filter selects only those SyncInfo that are not Pseudo-conflicts
+ */
+public class PseudoConflictFilter extends SyncInfoFilter {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SyncInfoFilter#select(org.eclipse.team.core.subscribers.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ return info.getKind() != 0 && (info.getKind() & SyncInfo.PSEUDO_CONFLICT) == 0;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SubscriberAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SubscriberAction.java
new file mode 100644
index 000000000..3819d947f
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SubscriberAction.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.internal.ui.actions.TeamAction;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+
+/**
+ * This is the abstract superclass for actions associated with a subscriber.
+ * It is not necessary that subscriber actions be subclasses of this class.
+ */
+public abstract class SubscriberAction extends TeamAction {
+
+ TeamSubscriber subscriber;
+
+ /**
+ * This method returns all instances of SyncResource that are in the current
+ * selection. For a table view, this is any resource that is directly selected.
+ * For a tree view, this is any descendants of the slected resource that are
+ * contained in the view.
+ *
+ * @return the selected resources
+ */
+ protected SyncResource[] getSyncResources() {
+ SyncResource[] syncResources = (SyncResource[])getSelectedResources(SyncResource.class);
+ return syncResources;
+ }
+
+ /**
+ * The default enablement behavior for subscriber actions is to enable
+ * the action if there is at least one SyncInfo in the selection
+ * for which the action is enabled (determined by invoking
+ * <code>isEnabled(SyncInfo)</code>).
+ * @see org.eclipse.team.internal.ui.actions.TeamAction#isEnabled()
+ */
+ protected boolean isEnabled() throws TeamException {
+ return (getFilteredSyncResources().length > 0);
+ }
+
+ /**
+ * Return true if the action should be enabled for the given SyncInfo.
+ * Default behavior is to use a SyncInfoFilter to determine if the action
+ * is enabled.
+ *
+ * @param info
+ * @return
+ */
+ protected boolean select(SyncResource resource) {
+ SyncInfo info = resource.getSyncInfo();
+ return info != null && getSyncInfoFilter().select(info);
+ }
+
+ /**
+ * @return
+ */
+ protected SyncInfoFilter getSyncInfoFilter() {
+ return new SyncInfoFilter();
+ }
+
+ /**
+ * Return the selected SyncInfo for which this action is enabled.
+ * @return
+ */
+ protected SyncResource[] getFilteredSyncResources() {
+ SyncResource[] resources = getSyncResources();
+ List filtered = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ SyncResource resource = resources[i];
+ if (select(resource))
+ filtered.add(resource);
+ }
+ return (SyncResource[]) filtered.toArray(new SyncResource[filtered.size()]);
+ }
+
+ /**
+ * @return
+ */
+ public TeamSubscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /**
+ * Sets
+ * @param context
+ */
+ public void setSubscriber(TeamSubscriber subscriber) {
+ this.subscriber = subscriber;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoChangeTypeFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoChangeTypeFilter.java
new file mode 100644
index 000000000..de133060d
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoChangeTypeFilter.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.sync.IRemoteSyncElement;
+
+/**
+ * Thsi class filters the SyncInfo by change type (addition, deletion, change)
+ */
+public class SyncInfoChangeTypeFilter extends SyncInfoFilter {
+
+ private int[] changeFilters = new int[] {IRemoteSyncElement.ADDITION, IRemoteSyncElement.DELETION, IRemoteSyncElement.CHANGE};
+
+ public SyncInfoChangeTypeFilter(int[] changeFilters) {
+ this.changeFilters = changeFilters;
+ }
+
+ public SyncInfoChangeTypeFilter(int change) {
+ this(new int[] { change });
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.SyncSetFilter#select(org.eclipse.team.core.sync.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ int syncKind = info.getKind();
+ for (int i = 0; i < changeFilters.length; i++) {
+ int filter = changeFilters[i];
+ if ((syncKind & SyncInfo.CHANGE_MASK) == filter)
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoDirectionFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoDirectionFilter.java
new file mode 100644
index 000000000..1ec79b327
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoDirectionFilter.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * Filter the SyncInfo by a set of directions (incoming, outgoing, conflict)
+ */
+public class SyncInfoDirectionFilter extends SyncInfoFilter {
+
+ int[] directionFilters = new int[] {SyncInfo.OUTGOING, SyncInfo.INCOMING, SyncInfo.CONFLICTING};
+
+ public SyncInfoDirectionFilter(int[] directionFilters) {
+ this.directionFilters = directionFilters;
+ }
+
+ public SyncInfoDirectionFilter(int direction) {
+ this(new int[] { direction });
+ }
+
+ /* (non-Javadoc)
+ * @see SyncSetFilter#select(org.eclipse.team.core.sync.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ int syncKind = info.getKind();
+ for (int i = 0; i < directionFilters.length; i++) {
+ int filter = directionFilters[i];
+ if ((syncKind & SyncInfo.DIRECTION_MASK) == filter)
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoFilter.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoFilter.java
new file mode 100644
index 000000000..92f16cfd5
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncInfoFilter.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import org.eclipse.team.core.subscribers.SyncInfo;
+
+/**
+ * A SyncInfoFilter is used by a SyncSetInput to detemine which resources
+ * should be part of the sync set.
+ */
+public class SyncInfoFilter {
+
+ public static SyncInfoFilter getDirectionAndChangeFilter(int direction, int change) {
+ return new AndSyncInfoFilter(new SyncInfoFilter[] {
+ new SyncInfoDirectionFilter(direction),
+ new SyncInfoChangeTypeFilter(change)
+ });
+ }
+
+ /**
+ * Return true if the provided SyncInfo matches the filter.
+ * The default behavior it to include resources whose syncKind
+ * is non-zero.
+ *
+ * @param info
+ * @return
+ */
+ public boolean select(SyncInfo info) {
+ return info.getKind() != 0;
+ }
+}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncResourceSet.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncResourceSet.java
new file mode 100644
index 000000000..92be4eb16
--- /dev/null
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/ui/sync/SyncResourceSet.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.ui.sync;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+
+/**
+ * @author Administrator
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+public class SyncResourceSet {
+
+ Set set = new HashSet();
+
+ public SyncResourceSet(SyncResource[] resources) {
+ set.addAll(Arrays.asList(resources));
+ }
+ /**
+ * Returns true if there are any conflicting nodes in the set, and
+ * false otherwise.
+ */
+ public boolean hasConflicts() {
+ return hasNodes(new SyncInfoDirectionFilter(SyncInfo.CONFLICTING));
+ }
+
+ /**
+ * Returns true if this sync set has incoming changes.
+ * Note that conflicts are not considered to be incoming changes.
+ */
+ public boolean hasIncomingChanges() {
+ return hasNodes(new SyncInfoDirectionFilter(SyncInfo.INCOMING));
+ }
+
+ /**
+ * Returns true if this sync set has outgoing changes.
+ * Note that conflicts are not considered to be outgoing changes.
+ */
+ public boolean hasOutgoingChanges() {
+ return hasNodes(new SyncInfoDirectionFilter(SyncInfo.OUTGOING));
+ }
+
+ /**
+ * Returns true if this sync set has auto-mergeable conflicts.
+ */
+ public boolean hasAutoMergeableConflicts() {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ if ((node.getKind() & SyncInfo.AUTOMERGE_CONFLICT) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all conflicting nodes from this set.
+ */
+ public void removeConflictingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.CONFLICTING));
+ }
+ /**
+ * Removes all outgoing nodes from this set.
+ */
+ public void removeOutgoingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.OUTGOING));
+ }
+ /**
+ * Removes all incoming nodes from this set.
+ */
+ public void removeIncomingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.INCOMING));
+ }
+
+ /**
+ * Removes all nodes from this set that are not auto-mergeable conflicts
+ */
+ public void removeNonMergeableNodes() {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ if ((node.getKind() & SyncInfo.MANUAL_CONFLICT) != 0) {
+ it.remove();
+ } else if (node.getChangeDirection() != SyncInfo.CONFLICTING) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Indicate whether the set has nodes matching the given filter
+ */
+ public boolean hasNodes(SyncInfoFilter filter) {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ SyncInfo info = node.getSyncInfo();
+ if (info != null && filter.select(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all nodes from this set that do not match the given filter
+ */
+ public void selectNodes(SyncInfoFilter filter) {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ SyncInfo info = node.getSyncInfo();
+ if (info == null || !filter.select(info)) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Removes all nodes from this set that match the given filter
+ */
+ public void rejectNodes(SyncInfoFilter filter) {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ SyncInfo info = node.getSyncInfo();
+ if (info != null && filter.select(info)) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Return all nodes in this set that match the given filter
+ */
+ public SyncResource[] getNodes(SyncInfoFilter filter) {
+ List result = new ArrayList();
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ SyncInfo info = node.getSyncInfo();
+ if (info != null && filter.select(info)) {
+ result.add(node);
+ }
+ }
+ return (SyncResource[]) result.toArray(new SyncResource[result.size()]);
+ }
+
+ /**
+ *
+ */
+ public SyncResource[] getSyncResources() {
+ return (SyncResource[]) set.toArray(new SyncResource[set.size()]);
+ }
+
+ /**
+ * Returns the resources from all the nodes in this set.
+ */
+ public IResource[] getResources() {
+ SyncResource[] changed = getSyncResources();
+ IResource[] resources = new IResource[changed.length];
+ for (int i = 0; i < changed.length; i++) {
+ resources[i] = changed[i].getResource();
+ }
+ return resources;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isEmpty() {
+ return set.isEmpty();
+ }
+
+ /**
+ * @param resources
+ */
+ public void removeResources(IResource[] resources) {
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ removeResource(resource);
+ }
+ }
+
+ /**
+ * @param resource
+ */
+ private void removeResource(IResource resource) {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ if (node.getResource().equals(resource)) {
+ it.remove();
+ // short-circuit the operation once a match is found
+ return;
+ }
+ }
+ }
+ /**
+ * @return
+ */
+ public int size() {
+ return set.size();
+ }
+ /**
+ * @param file
+ * @return
+ */
+ public SyncResource getNodeFor(IResource resource) {
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ SyncResource node = (SyncResource)it.next();
+ if (node.getResource().equals(resource)) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/AllTestsTeamSubscriber.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/AllTestsTeamSubscriber.java
new file mode 100644
index 000000000..d84295e92
--- /dev/null
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/AllTestsTeamSubscriber.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.tests.ccvs.core.subscriber;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.eclipse.team.tests.ccvs.core.CVSTestSetup;
+import org.eclipse.team.tests.ccvs.core.EclipseTest;
+
+public class AllTestsTeamSubscriber extends EclipseTest {
+
+ public AllTestsTeamSubscriber() {
+ super();
+ }
+
+ public AllTestsTeamSubscriber(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTest(CVSMergeSubscriberTest.suite());
+ suite.addTest(CVSWorkspaceSubscriberTest.suite());
+ return new CVSTestSetup(suite);
+ }
+}
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSMergeSubscriberTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSMergeSubscriberTest.java
new file mode 100644
index 000000000..67221c471
--- /dev/null
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSMergeSubscriberTest.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.tests.ccvs.core.subscriber;
+
+import java.io.IOException;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSMergeSubscriber;
+import org.eclipse.team.internal.ccvs.core.CVSTag;
+import org.eclipse.team.internal.ccvs.core.client.Command;
+import org.eclipse.team.internal.ccvs.ui.subscriber.MergeUpdateAction;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.tests.ccvs.core.CVSTestSetup;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+
+/**
+ * Tests the CVSMergeSubscriber
+ */
+public class CVSMergeSubscriberTest extends CVSSyncSubscriberTest {
+
+ public static Test suite() {
+ String testName = System.getProperty("eclipse.cvs.testName");
+ if (testName == null) {
+ TestSuite suite = new TestSuite(CVSMergeSubscriberTest.class);
+ return new CVSTestSetup(suite);
+ } else {
+ return new CVSTestSetup(new CVSMergeSubscriberTest(testName));
+ }
+ }
+
+ public CVSMergeSubscriberTest() {
+ super();
+ }
+
+ public CVSMergeSubscriberTest(String name) {
+ super(name);
+ }
+
+ private IProject branchProject(IProject project, CVSTag root, CVSTag branch) throws TeamException {
+ IProject copy = checkoutCopy(project, "-copy");
+ tagProject(project, root);
+ tagProject(project, branch);
+ getProvider(copy).update(new IResource[] {copy}, Command.NO_LOCAL_OPTIONS,
+ branch, false /*createBackups*/, DEFAULT_MONITOR);
+ return copy;
+ }
+
+ /**
+ * Perform a merge on the given resources
+ * @param subscriber
+ * @param project
+ * @param strings
+ */
+ private void mergeResources(CVSMergeSubscriber subscriber, IProject project, String[] resourcePaths) throws CoreException, TeamException {
+ IResource[] resources = getResources(project, resourcePaths);
+ SyncResource[] syncResources = createSyncResources(subscriber, resources);
+ mergeResources(subscriber, syncResources);
+ }
+
+ /**
+ * @param syncResources
+ */
+ private void mergeResources(TeamSubscriber subscriber, SyncResource[] syncResources) throws CVSException {
+ MergeUpdateAction action = new MergeUpdateAction() {
+ protected boolean promptForOverwrite(SyncResourceSet syncSet) {
+ // Agree to overwrite any conflicting resources
+ return true;
+ }
+ };
+ action.setSubscriber(subscriber);
+ action.run(new SyncResourceSet(syncResources), DEFAULT_MONITOR);
+ }
+
+ /**
+ * Test the basic incoming changes cases
+ * - incoming addition
+ * - incoming deletion
+ * - incoming change
+ * - incoming addition of a folder containing files
+ */
+ public void testIncomingChanges() throws CoreException, TeamException {
+ // Create a test project
+ IProject project = createProject("testIncomingChanges", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Checkout and branch a copy
+ CVSTag root = new CVSTag("root_branch1", CVSTag.VERSION);
+ CVSTag branch = new CVSTag("branch1", CVSTag.BRANCH);
+ IProject copy = branchProject(project, root, branch);
+
+ // Modify the branch
+ addResources(copy, new String[] {"addition.txt", "folderAddition/", "folderAddition/new.txt"}, true);
+ deleteResources(copy, new String[] {"folder1/a.txt"}, true);
+ changeResources(copy, new String[] {"file1.txt"}, true);
+
+ // create a merge subscriber
+ CVSMergeSubscriber subscriber = new CVSMergeSubscriber(new IResource[] { project }, root, branch);
+
+ // check the sync states
+ assertSyncEquals("testIncomingChanges", subscriber, project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "addition.txt", "folderAddition/", "folderAddition/new.txt"},
+ true, new int[] {
+ SyncInfo.INCOMING | SyncInfo.CHANGE,
+ SyncInfo.IN_SYNC,
+ SyncInfo.INCOMING | SyncInfo.DELETION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION});
+
+ // Perform a merge
+ mergeResources(subscriber, project, new String[] {
+ "file1.txt",
+ "folder1/a.txt",
+ "addition.txt",
+ "folderAddition/",
+ "folderAddition/new.txt"});
+
+ // check the sync states for the workspace subscriber
+ assertSyncEquals("testIncomingChanges", getWorkspaceSubscriber(), project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "addition.txt", "folderAddition/", "folderAddition/new.txt"},
+ true, new int[] {
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+ }
+
+ public void testMergableConflicts() throws TeamException, CVSException, CoreException, IOException {
+ // Create a test project
+ IProject project = createProject("testMergableConflicts", new String[] { "file1.txt", "file2.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+ setContentsAndEnsureModified(project.getFile("file1.txt"), "some text\nwith several lines\n");
+ setContentsAndEnsureModified(project.getFile("file2.txt"), "some text\nwith several lines\n");
+ commitProject(project);
+
+ // Checkout and branch a copy
+ CVSTag root = new CVSTag("root_branch1", CVSTag.VERSION);
+ CVSTag branch = new CVSTag("branch1", CVSTag.BRANCH);
+ IProject branchedProject = branchProject(project, root, branch);
+
+ // modify the branch
+ appendText(branchedProject.getFile("file1.txt"), "first line\n", true);
+ appendText(branchedProject.getFile("file2.txt"), "last line\n", false);
+ commitProject(branchedProject);
+
+ // modify HEAD
+ appendText(project.getFile("file1.txt"), "last line\n", false);
+ commitProject(project);
+ // have one local change
+ appendText(project.getFile("file2.txt"), "first line\n", true);
+
+ // create a merge subscriber
+ CVSMergeSubscriber subscriber = new CVSMergeSubscriber(new IResource[] { project }, root, branch);
+
+ // check the sync states
+ assertSyncEquals("testMergableConflicts", subscriber, project,
+ new String[] { "file1.txt", "file2.txt"},
+ true, new int[] {
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE});
+
+ // Perform a merge
+ mergeResources(subscriber, project, new String[] {
+ "file1.txt",
+ "file2.txt"});
+
+ // check the sync states for the workspace subscriber
+ assertSyncEquals("testMergableConflicts", getWorkspaceSubscriber(), project,
+ new String[] { "file1.txt", "file2.txt"},
+ true, new int[] {
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.CHANGE});
+
+ //TODO: How do we know if the right thing happened to the file contents?
+ }
+
+ public void testUnmergableConflicts() throws TeamException, CVSException, CoreException, IOException {
+ // Create a test project
+ IProject project = createProject("testUnmergableConflicts", new String[] { "delete.txt", "file1.txt", "file2.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+ setContentsAndEnsureModified(project.getFile("file1.txt"), "some text\nwith several lines\n");
+ setContentsAndEnsureModified(project.getFile("file2.txt"), "some text\nwith several lines\n");
+ commitProject(project);
+
+ // Checkout and branch a copy
+ CVSTag root = new CVSTag("root_branch1", CVSTag.VERSION);
+ CVSTag branch = new CVSTag("branch1", CVSTag.BRANCH);
+ IProject branchedProject = branchProject(project, root, branch);
+
+ // modify the branch
+ appendText(branchedProject.getFile("file1.txt"), "first line\n", true);
+ appendText(branchedProject.getFile("file2.txt"), "last line\n", false);
+ addResources(branchedProject, new String[] {"addition.txt"}, false);
+ deleteResources(branchedProject, new String[] {"delete.txt", "folder1/a.txt"}, false);
+ setContentsAndEnsureModified(branchedProject.getFile("folder1/b.txt"));
+ commitProject(branchedProject);
+
+ // modify local workspace
+ appendText(project.getFile("file1.txt"), "conflict line\n", true);
+ setContentsAndEnsureModified(project.getFile("folder1/a.txt"));
+ deleteResources(project, new String[] {"delete.txt", "folder1/b.txt"}, false);
+ addResources(project, new String[] {"addition.txt"}, false);
+ appendText(project.getFile("file2.txt"), "conflict line\n", false);
+
+ // create a merge subscriber
+ CVSMergeSubscriber subscriber = new CVSMergeSubscriber(new IResource[] { project }, root, branch);
+
+ // check the sync states
+ assertSyncEquals("testUnmergableConflicts", subscriber, project,
+ new String[] { "delete.txt", "file1.txt", "file2.txt", "addition.txt", "folder1/a.txt", "folder1/b.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC, /* TODO: is this OK */
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE});
+
+ // TODO: Should actually perform the merge and check the results
+ // However, this would require the changes to be redone
+
+ // commit to modify HEAD
+ commitProject(project);
+
+ // check the sync states
+ assertSyncEquals("testUnmergableConflicts", subscriber, project,
+ new String[] { "delete.txt", "file1.txt", "file2.txt", "addition.txt", "folder1/a.txt", "folder1/b.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC, /* TODO: is this OK */
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE});
+
+ // Perform a merge
+ mergeResources(subscriber, project, new String[] { "delete.txt", "file1.txt", "file2.txt", "addition.txt", "folder1/a.txt", "folder1/b.txt"});
+
+ // check the sync states for the workspace subscriber
+ assertSyncEquals("testUnmergableConflicts", getWorkspaceSubscriber(), project,
+ new String[] { "file1.txt", "file2.txt", "addition.txt", "folder1/a.txt", "folder1/b.txt"},
+ true, new int[] {
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+ assertDeleted("testUnmergableConflicts", project, new String[] { "delete.txt" });
+
+ //TODO: How do we know if the right thing happend to the file contents?
+ }
+
+}
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSSyncSubscriberTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSSyncSubscriberTest.java
new file mode 100644
index 000000000..4f5548991
--- /dev/null
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSSyncSubscriberTest.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.tests.ccvs.core.subscriber;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.AssertionFailedError;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.ITeamResourceChangeListener;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.core.subscribers.TeamDelta;
+import org.eclipse.team.core.subscribers.TeamProvider;
+import org.eclipse.team.core.sync.RemoteSyncElement;
+import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.internal.ui.sync.views.SyncSet;
+import org.eclipse.team.tests.ccvs.core.EclipseTest;
+
+/**
+ * Provides test methods common to CVS sync subscribers
+ */
+public abstract class CVSSyncSubscriberTest extends EclipseTest {
+
+ private ITeamResourceChangeListener listener;
+ private List accumulatedTeamDeltas = new ArrayList();
+
+ public CVSSyncSubscriberTest() {
+ super();
+ }
+
+ public CVSSyncSubscriberTest(String name) {
+ super(name);
+ }
+
+ protected TeamSubscriber getWorkspaceSubscriber() throws TeamException {
+ TeamSubscriber subscriber = TeamProvider.getSubscriber(CVSProviderPlugin.CVS_WORKSPACE_SUBSCRIBER_ID);
+ if (subscriber == null) fail("The CVS sync subsciber is not registered");
+ return subscriber;
+ }
+
+ /*
+ * Refresh the subscriber for the given resource
+ */
+ protected void refresh(TeamSubscriber subscriber, IResource resource) throws TeamException {
+ subscriber.refresh(new IResource[] { resource}, IResource.DEPTH_INFINITE, DEFAULT_MONITOR);
+ }
+
+ /*
+ * Assert that the specified resources in the subscriber have the specified sync kind
+ * Ignore conflict types if they are not specified in the assert statement
+ */
+ protected void assertSyncEquals(String message, TeamSubscriber subscriber, IContainer root, String[] resourcePaths, boolean refresh, int[] syncKinds) throws CoreException, TeamException {
+ assertTrue(resourcePaths.length == syncKinds.length);
+ if (refresh) refresh(subscriber, root);
+ IResource[] resources = getResources(root, resourcePaths);
+ for (int i=0;i<resources.length;i++) {
+ assertSyncEquals(message, subscriber, resources[i], syncKinds[i]);
+ }
+
+ }
+
+ protected void assertSyncEquals(String message, TeamSubscriber subscriber, IResource resource, int syncKind) throws TeamException {
+ int conflictTypeMask = 0x0F; // ignore manual and auto merge sync types for now.
+ SyncInfo info = subscriber.getSyncInfo(resource, DEFAULT_MONITOR);
+ int kind;
+ int kindOther = syncKind & conflictTypeMask;
+ if (info == null) {
+ kind = SyncInfo.IN_SYNC;
+ } else {
+ kind = info.getKind() & conflictTypeMask;
+ }
+ assertTrue(message + ": improper sync state for " + resource + " expected " +
+ RemoteSyncElement.kindToString(kindOther) + " but was " +
+ RemoteSyncElement.kindToString(kind), kind == kindOther);
+ }
+
+ /**
+ * @param changes
+ * @param resources
+ */
+ protected void assertSyncChangesMatch(TeamDelta[] changes, IResource[] resources) {
+ // First, ensure that all the resources appear in the delta
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ boolean found = false;
+ for (int j = 0; j < changes.length; j++) {
+ TeamDelta delta = changes[j];
+ if (delta.getResource().equals(resource)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("No change reported for " + resource, found);
+ }
+ // TODO: We'll worry about extra deltas later
+// // Next, ensure there are no extra deltas
+// List changedResources = new ArrayList(resources.length);
+// changedResources.addAll(Arrays.asList(resources));
+// for (int i = 0; i < changes.length; i++) {
+// TeamDelta change = changes[i];
+// IResource resource = change.getResource();
+// assertTrue("Unanticipated change reported for " + resource, changedResources.contains(resource));
+// }
+ }
+
+ /*
+ * Assert that the named resources have no local resource or sync info
+ */
+ protected void assertDeleted(String message, IContainer root, String[] resourcePaths) throws CoreException, TeamException {
+ IResource[] resources = getResources(root, resourcePaths);
+ for (int i=0;i<resources.length;i++) {
+ try {
+ if (! resources[i].exists())
+ break;
+ } catch (AssertionFailedError e) {
+ break;
+ }
+ assertTrue(message + ": resource " + resources[i] + " still exists in some form", false);
+ }
+ }
+
+ public static class ResourceCondition {
+ public boolean matches(IResource resource) throws CoreException, TeamException {
+ return true;
+ }
+ }
+
+ protected IResource[] collect(IResource[] resources, final ResourceCondition condition, int depth) throws CoreException, TeamException {
+ final Set affected = new HashSet();
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (resource.exists() || resource.isPhantom()) {
+ resource.accept(new IResourceVisitor() {
+ public boolean visit(IResource r) throws CoreException {
+ try {
+ if (condition.matches(r)) {
+ affected.add(r);
+ }
+ } catch (TeamException e) {
+ throw new CoreException(e.getStatus());
+ }
+ return true;
+ }
+ }, depth, true /* include phantoms */);
+ } else {
+ if (condition.matches(resource)) {
+ affected.add(resource);
+ }
+ }
+ }
+ return (IResource[]) affected.toArray(new IResource[affected.size()]);
+ }
+
+ /**
+ * @param resources
+ * @param condition
+ * @return
+ */
+ protected IResource[] collectAncestors(IResource[] resources, ResourceCondition condition) throws CoreException, TeamException {
+ Set affected = new HashSet();
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ while (resource.getType() != IResource.ROOT) {
+ if (condition.matches(resource)) {
+ affected.add(resource);
+ } else {
+ break;
+ }
+ resource = resource.getParent();
+ }
+ }
+ return (IResource[]) affected.toArray(new IResource[affected.size()]);
+ }
+
+ protected TeamDelta[] deregisterSubscriberListener(TeamSubscriber subscriber) throws TeamException {
+ subscriber.removeListener(listener);
+ return (TeamDelta[]) accumulatedTeamDeltas.toArray(new TeamDelta[accumulatedTeamDeltas.size()]);
+ }
+
+ protected ITeamResourceChangeListener registerSubscriberListener(TeamSubscriber subscriber) throws TeamException {
+ listener = new ITeamResourceChangeListener() {
+ public void teamResourceChanged(TeamDelta[] deltas) {
+ accumulatedTeamDeltas.addAll(Arrays.asList(deltas));
+ }
+ };
+ accumulatedTeamDeltas.clear();
+ subscriber.addListener(listener);
+ return listener;
+ }
+
+ /**
+ * @param resources
+ */
+ protected SyncResource[] createSyncResources(TeamSubscriber subscriber, IResource[] resources) throws TeamException {
+ // TODO: SyncResources needs a SyncSet which contains the SyncInfo
+ // but SyncSet is not API
+ SyncSet syncSet = new SyncSet();
+ SyncResource[] result = new SyncResource[resources.length];
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ syncSet.add(subscriber.getSyncInfo(resource, DEFAULT_MONITOR));
+ result[i] = new SyncResource(syncSet, resource);
+ }
+ return result;
+ }
+}
diff --git a/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java
new file mode 100644
index 000000000..1effb5094
--- /dev/null
+++ b/tests/org.eclipse.team.tests.cvs.core/src/org/eclipse/team/tests/ccvs/core/subscriber/CVSWorkspaceSubscriberTest.java
@@ -0,0 +1,1034 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.tests.ccvs.core.subscriber;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.SyncInfo;
+import org.eclipse.team.core.subscribers.TeamSubscriber;
+import org.eclipse.team.core.subscribers.TeamDelta;
+import org.eclipse.team.internal.ccvs.core.CVSException;
+import org.eclipse.team.internal.ccvs.core.CVSTag;
+import org.eclipse.team.internal.ccvs.core.ICVSFolder;
+import org.eclipse.team.internal.ccvs.core.ICVSResource;
+import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
+import org.eclipse.team.internal.ccvs.ui.subscriber.SubscriberCommitAction;
+import org.eclipse.team.internal.ccvs.ui.subscriber.WorkspaceUpdateAction;
+import org.eclipse.team.internal.ui.sync.views.SyncResource;
+import org.eclipse.team.tests.ccvs.core.CVSTestSetup;
+import org.eclipse.team.ui.sync.SyncResourceSet;
+
+/**
+ * This class tests the CVSWorkspaceSubscriber
+ */
+public class CVSWorkspaceSubscriberTest extends CVSSyncSubscriberTest {
+
+ /**
+ * Constructor for CVSProviderTest
+ */
+ public CVSWorkspaceSubscriberTest() {
+ super();
+ }
+
+ /**
+ * Constructor for CVSProviderTest
+ */
+ public CVSWorkspaceSubscriberTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ String testName = System.getProperty("eclipse.cvs.testName");
+ if (testName == null) {
+ TestSuite suite = new TestSuite(CVSWorkspaceSubscriberTest.class);
+ return new CVSTestSetup(suite);
+ } else {
+ return new CVSTestSetup(new CVSWorkspaceSubscriberTest(testName));
+ }
+ }
+
+ private TeamSubscriber getSubscriber() throws TeamException {
+ return getWorkspaceSubscriber();
+ }
+
+ /* (non-Javadoc)
+ *
+ * Override to check that the proper sync state is achieved.
+ *
+ * @see org.eclipse.team.tests.ccvs.core.EclipseTest#setContentsAndEnsureModified(org.eclipse.core.resources.IFile)
+ */
+ protected void setContentsAndEnsureModified(IFile file) throws CoreException, TeamException {
+ // The delta will indicate to any interested parties that the sync state of the
+ // file has changed
+ super.setContentsAndEnsureModified(file);
+ assertSyncEquals("Setting contents: ", file, SyncInfo.OUTGOING | SyncInfo.CHANGE);
+ }
+
+ private void assertSyncEquals(String string, IProject project, String[] strings, boolean b, int[] is) throws CoreException, TeamException {
+ assertSyncEquals(string, getSubscriber(), project, strings, b, is);
+ }
+
+ private void assertSyncEquals(String message, IResource resource, int depth) throws TeamException {
+ assertSyncEquals(message, getSubscriber(), resource, depth);
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.tests.ccvs.core.EclipseTest#addResources(org.eclipse.core.resources.IResource[])
+ */
+ protected void addResources(IResource[] resources) throws TeamException, CVSException, CoreException {
+ // first, get affected children
+ IResource[] affectedChildren = collect(resources, new ResourceCondition() {
+ public boolean matches(IResource resource) throws CoreException, TeamException {
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ return (!cvsResource.isManaged() && !cvsResource.isIgnored());
+ }
+ }, IResource.DEPTH_INFINITE);
+ // also get affected parents
+ IResource[] affectedParents = collectAncestors(resources, new ResourceCondition() {
+ public boolean matches(IResource resource) throws CoreException, TeamException {
+ if (resource.getType() == IResource.PROJECT) return false;
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ return (!cvsResource.isManaged() && !cvsResource.isIgnored());
+ }
+ });
+ Set affected = new HashSet();
+ affected.addAll(Arrays.asList(affectedChildren));
+ affected.addAll(Arrays.asList(affectedParents));
+
+ registerSubscriberListener();
+ super.addResources(resources);
+ TeamDelta[] changes = deregisterSubscriberListener();
+ assertSyncChangesMatch(changes, (IResource[]) affected.toArray(new IResource[affected.size()]));
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (resource.getType() == IResource.FILE) {
+ assertSyncEquals("Add", resource, SyncInfo.OUTGOING | SyncInfo.ADDITION);
+ } else {
+ // TODO: a folder should be in sync but isn't handled properly
+ assertSyncEquals("Add", resource, SyncInfo.IN_SYNC);
+ }
+
+ }
+ }
+
+ /**
+ *
+ */
+ private void registerSubscriberListener() throws TeamException {
+ registerSubscriberListener(getSubscriber());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.tests.ccvs.core.EclipseTest#deleteResources(org.eclipse.core.resources.IResource[])
+ */
+ protected void deleteResources(IResource[] resources) throws TeamException, CoreException {
+ IResource[] affected = collect(resources, new ResourceCondition(), IResource.DEPTH_INFINITE);
+ registerSubscriberListener();
+ super.deleteResources(resources);
+ TeamDelta[] changes = deregisterSubscriberListener();
+ assertSyncChangesMatch(changes, affected);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ assertSyncEquals("Delete", resource, SyncInfo.OUTGOING | SyncInfo.DELETION);
+ }
+ }
+
+ /**
+ * @return
+ */
+ private TeamDelta[] deregisterSubscriberListener() throws TeamException {
+ return deregisterSubscriberListener(getSubscriber());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.tests.ccvs.core.EclipseTest#commitResources(org.eclipse.core.resources.IResource[])
+ */
+ protected void commitResources(IResource[] resources, int depth) throws TeamException, CVSException, CoreException {
+ IResource[] affected = collect(resources, new ResourceCondition() {
+ public boolean matches(IResource resource) throws CoreException, TeamException {
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ return (!cvsResource.isFolder() && cvsResource.isManaged() && cvsResource.isModified(DEFAULT_MONITOR));
+ }
+ }, IResource.DEPTH_INFINITE);
+ registerSubscriberListener();
+ super.commitResources(resources, depth);
+ TeamDelta[] changes = deregisterSubscriberListener();
+ assertSyncChangesMatch(changes, affected);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (resource.exists())
+ assertSyncEquals("Commit", resource, SyncInfo.IN_SYNC);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.tests.ccvs.core.EclipseTest#unmanageResources(org.eclipse.core.resources.IResource[])
+ */
+ protected void unmanageResources(IResource[] resources) throws CoreException, TeamException {
+ IResource[] affected = collect(resources, new ResourceCondition() {
+ public boolean matches(IResource resource) throws CoreException, TeamException {
+ ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
+ return (cvsResource.isManaged());
+ }
+ }, IResource.DEPTH_INFINITE);
+ registerSubscriberListener();
+ super.unmanageResources(resources);
+ TeamDelta[] changes = deregisterSubscriberListener();
+ assertSyncChangesMatch(changes, affected);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (resource.exists())
+ assertSyncEquals("Unmanage", resource, SyncInfo.IN_SYNC);
+ }
+ }
+
+ /**
+ * Update the resources from an existing container with the changes from the CVS repository.
+ * This update uses the SubscriberUpdateAction to perform the update so that all special
+ * cases should be handled properly
+ */
+ public IResource[] updateResources(IContainer container, String[] hierarchy, boolean ignoreLocalChanges) throws CoreException, TeamException {
+ IResource[] resources = getResources(container, hierarchy);
+ SyncResource[] syncResources = createSyncResources(resources);
+ updateResources(syncResources);
+ return resources;
+ }
+
+ /**
+ * @param resources
+ * @return
+ */
+ private SyncResource[] createSyncResources(IResource[] resources) throws TeamException {
+ return createSyncResources(getSubscriber(), resources);
+ }
+
+ /**
+ * Commit the resources from an existing container to the CVS repository.
+ * This commit uses the SubscriberCommitAction to perform the commit so that all special
+ * cases should be handled properly
+ */
+ public IResource[] commitResources(IContainer container, String[] hierarchy) throws CoreException, TeamException {
+ IResource[] resources = getResources(container, hierarchy);
+ SyncResource[] syncResources = createSyncResources(resources);
+ commitResources(syncResources);
+ return resources;
+ }
+
+ /**
+ * @param syncResources
+ */
+ private void updateResources(SyncResource[] syncResources) throws CVSException {
+ new WorkspaceUpdateAction().run(new SyncResourceSet(syncResources), DEFAULT_MONITOR);
+ }
+
+ /**
+ * @param syncResources
+ */
+ private void commitResources(SyncResource[] syncResources) throws CVSException {
+ new SubscriberCommitAction().run(new SyncResourceSet(syncResources), DEFAULT_MONITOR);
+ }
+
+ /*
+ * Perform a simple test that checks for the different types of incoming changes
+ */
+ public void testIncomingChanges() throws IOException, CoreException, TeamException {
+ // Create a test project
+ IProject project = createProject("testIncomingChanges", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Checkout and modify a copy
+ IProject copy = checkoutCopy(project, "-copy");
+ setContentsAndEnsureModified(copy.getFile("folder1/a.txt"));
+ addResources(copy, new String[] { "folder2/folder3/add.txt" }, false);
+ deleteResources(copy, new String[] {"folder1/b.txt"}, false);
+ commitProject(copy);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testIncomingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.INCOMING | SyncInfo.CHANGE,
+ SyncInfo.INCOMING | SyncInfo.DELETION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION});
+
+ // Catch up to the incoming changes
+ updateResources(
+ project,
+ new String[] {
+ "folder1/a.txt",
+ "folder1/b.txt",
+ "folder2/",
+ "folder2/folder3/",
+ "folder2/folder3/add.txt"},
+ false /* ignore local changes */);
+
+ // Verify that we are in sync (except for "folder1/b.txt", which was deleted)
+ assertSyncEquals("testIncomingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+
+ // Ensure "folder1/b.txt" was deleted
+ assertDeleted("testIncomingChanges", project, new String[] {"folder1/b.txt"});
+
+ // Verify that the copy equals the original
+ assertEquals(project, copy);
+ }
+
+ /*
+ * Perform a simple test that checks for the different types of outgoing changes
+ */
+ public void testOutgoingChanges() throws TeamException, CoreException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testOutgoingChanges", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Make some modifications
+ setContentsAndEnsureModified(project.getFile("folder1/a.txt"));
+ addResources(project, new String[] { "folder2/folder3/add.txt" }, false);
+ deleteResources(project, new String[] {"folder1/b.txt"}, false);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testOutgoingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.IN_SYNC, /* adding a folder creates it remotely */
+ SyncInfo.IN_SYNC, /* adding a folder creates it remotely */
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ // Commit the changes
+ commitResources(project, new String[] {"folder1/a.txt", "folder1/b.txt", "folder2/folder3/add.txt"});
+
+ // Ensure we're in sync
+ assertSyncEquals("testOutgoingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+
+ // Ensure deleted resource "folder1/b.txt" no longer exists
+ assertDeleted("testOutgoingChanges", project, new String[] {"folder1/b.txt"});
+ }
+
+ /*
+ * Perform a simple test that checks for the different types of outgoing changes
+ */
+ public void testOverrideOutgoingChanges() throws CoreException, TeamException, IOException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testOverrideOutgoingChanges", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+ // Checkout a copy for later verification
+ IProject original = checkoutCopy(project, "-copy");
+
+ // Make some modifications
+ setContentsAndEnsureModified(project.getFile("folder1/a.txt"));
+ addResources(project, new String[] { "folder2/folder3/add.txt" }, false);
+ deleteResources(project, new String[] {"folder1/b.txt"}, false);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testOverrideOutgoingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.CHANGE,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.IN_SYNC, /* adding a folder creates it remotely */
+ SyncInfo.IN_SYNC, /* adding a folder creates it remotely */
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ // Commit the changes
+ updateResources(
+ project,
+ new String[] {
+ "folder1/a.txt",
+ "folder1/b.txt",
+ "folder2/folder3/add.txt"},
+ true /* ignore local changes */);
+
+ // Ensure added resources no longer exist
+ assertDeleted("testOverrideOutgoingChanges", project, new String[] {"folder2/", "folder2/folder3/","folder2/folder3/add.txt"});
+
+ // Ensure other resources are in sync
+ assertSyncEquals("testOverrideOutgoingChanges", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder2/"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+
+ // Verify that the original has reverted to its original contents
+ assertEquals(project, original);
+ }
+
+ /*
+ * Perform a test that checks for outgoing changes that are CVS questionables (no add or remove)
+ */
+ public void testOutgoingQuestionables() throws TeamException, CoreException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testIncomingChanges", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Create a new file without adding it to version control
+ buildResources(project, new String[] {"folder2/folder3/add.txt"}, false);
+
+ // Delete a file without an explicit cvs remove
+ // NOTE: This will result in an implicit cvs remove
+ IFile file = project.getFile("folder1/b.txt");
+ file.delete(true, DEFAULT_MONITOR);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testOutgoingQuestionables", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ commitResources(project, new String[] {"folder1/b.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"});
+
+ // Ensure we are in sync
+ assertSyncEquals("testOutgoingQuestionables", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder2/", "folder2/folder3/", "folder2/folder3/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+
+ // Ensure "folder1/b.txt" was deleted
+ assertDeleted("testOutgoingQuestionables", project, new String[] {"folder1/b.txt"});
+ }
+
+ /*
+ * Test simple file conflicts
+ */
+ public void testFileConflict() throws TeamException, CoreException, IOException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testFileConflict", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Checkout a copy and make some modifications
+ IProject copy = checkoutCopy(project, "-copy");
+ appendText(copy.getFile("file1.txt"), "prefix\n", true);
+ setContentsAndEnsureModified(copy.getFile("folder1/a.txt"), "Use a custom string to avoid intermitant errors!");
+ commitProject(copy);
+
+ // Make the same modifications to the original (We need to test both M and C!!!)
+ appendText(project.getFile("file1.txt"), "\npostfix", false); // This will test merges (M)
+ setContentsAndEnsureModified(project.getFile("folder1/a.txt"));
+
+ // Get the sync tree for the project
+ assertSyncEquals("testFileConflict", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt"},
+ true, new int[] {
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.IN_SYNC,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE });
+
+ // Catch up to the file1.txt conflict using UPDATE with ignoreLocalChanges
+ updateResources(
+ project,
+ new String[] {"file1.txt"},
+ true /* ignore local changes */);
+
+ assertSyncEquals("testFileConflict", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE });
+
+ // Release the folder1/a.txt conflict by merging and then committing
+ commitResources(project, new String[] {"folder1/a.txt"});
+
+ assertSyncEquals("testFileConflict", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+ }
+
+ /*
+ * Test conflicts involving additions
+ */
+ public void testAdditionConflicts() throws TeamException, CoreException {
+
+ // CASE 1: The user adds (using CVS add) a remotely added file
+ // (a) catchup is simply get?
+ // (b) release must do a merge
+ // CASE 2: The user adds (but not using cvs add) a remotely added file
+ // (a) catchup is simply get?
+ // (b) release must do a merge
+ // CASE 3: The user adds a remotely added then deleted file
+ // catchup is not applicable
+ // release is normal
+
+ // Create a test project (which commits it as well) and add an uncommited resource
+ IProject project = createProject("testAdditionConflicts", new String[] { "file.txt"});
+ addResources(project, new String[] { "add1a.txt", "add1b.txt" }, false);
+ addResources(project, new String[] { "add3.txt" }, false);
+ buildResources(project, new String[] {"add2a.txt", "add2b.txt"}, false);
+
+ // Checkout a copy, add the same resource and commit
+ IProject copy = checkoutCopy(project, "-copy");
+ addResources(copy, new String[] { "add1a.txt", "add1b.txt", "add2a.txt", "add2b.txt", "add3.txt"}, true);
+ deleteResources(copy, new String[] { "add3.txt"}, true);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testAdditionConflicts", project,
+ new String[] { "file.txt", "add1a.txt", "add1b.txt", "add2a.txt", "add2b.txt", "add3.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION });
+
+ // Commit conflicting add1b.txt and add2b.txt and outgoing add3.txt
+ commitResources(project, new String[]{"add1b.txt", "add2b.txt", "add3.txt"});
+
+ assertSyncEquals("testAdditionConflicts", project,
+ new String[] { "file.txt", "add1b.txt", "add2b.txt", "add3.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+
+ // Catch-up to conflicting cases using UPDATE
+ updateResources(
+ project,
+ new String[] {"add1a.txt", "add2a.txt"},
+ true /* ignore local changes */);
+
+
+ assertSyncEquals("testAdditionConflicts", project,
+ new String[] { "add1a.txt", "add2a.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+ }
+
+ /*
+ * Test conflicts involving deletions
+ */
+ public void testDeletionConflicts() throws TeamException, CoreException {
+
+ // CASE 1: The user deletes a remotely modified file
+ // (a) catchup must do an update
+ // (b) release must do a merge
+ // CASE 2: The user deletes (and removes) a remotely modified file
+ // (a) catchup must do an unmanage and update
+ // (b) release must do a merge
+ // CASE 3: The user modified a remotely deleted file
+ // (a) catchup must do an unmanage and local delete
+ // (b) release must do a merge
+ // CASE 4: The user deletes a remotely deleted file
+ // (a) catchup can update (or unmanage?)
+ // (b) release must unmanage
+ // CASE 5: The user deletes (and removes) a remotely deleted file
+ // (a) catchup can update (or unmanage?)
+ // (b) release must unmanage
+
+ // Perform the test case for case A first
+
+ // Create a test project (which commits it as well) and delete the resource without committing
+ IProject project = createProject("testDeletionConflictsA", new String[] { "delete1.txt", "delete2.txt", "delete3.txt", "delete4.txt", "delete5.txt"});
+ IFile file = project.getFile("delete1.txt"); // WARNING: This does a "cvs remove"!!!
+ file.delete(false, DEFAULT_MONITOR);
+ deleteResources(project, new String[] {"delete2.txt"}, false);
+ setContentsAndEnsureModified(project.getFile("delete3.txt"));
+ file = project.getFile("delete4.txt");
+ file.delete(false, DEFAULT_MONITOR);
+ deleteResources(project, new String[] {"delete5.txt"}, false);
+
+ // Checkout a copy and commit the deletion
+ IProject copy = checkoutCopy(project, "-copy");
+ setContentsAndEnsureModified(copy.getFile("delete1.txt"));
+ setContentsAndEnsureModified(copy.getFile("delete2.txt"));
+ deleteResources(copy, new String[] {"delete3.txt", "delete4.txt", "delete5.txt"}, false);
+ commitProject(copy);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testDeletionConflictsA", project,
+ new String[] { "delete1.txt", "delete2.txt", "delete3.txt", "delete4.txt", "delete5.txt"},
+ true, new int[] {
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+
+ // Catch up to remote changes.
+ updateResources(
+ project,
+ new String[] {
+ "delete1.txt",
+ "delete2.txt",
+ "delete3.txt",
+ "delete4.txt",
+ "delete5.txt"},
+ true /* ignore local changes */);
+
+ assertSyncEquals("testDeletionConflictsA", project,
+ new String[] { "delete1.txt", "delete2.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+
+ assertDeleted("testDeletionConflictsA", project, new String[] {"delete3.txt", "delete4.txt", "delete5.txt"});
+
+ // Now redo the test case for case B
+
+ // Create a test project (which commits it as well) and delete the resource without committing
+ project = createProject("testDeletionConflictsB", new String[] { "delete1.txt", "delete2.txt", "delete3.txt", "delete4.txt", "delete5.txt"});
+ file = project.getFile("delete1.txt");
+ file.delete(false, DEFAULT_MONITOR);
+ deleteResources(project, new String[] {"delete2.txt"}, false);
+ setContentsAndEnsureModified(project.getFile("delete3.txt"));
+ file = project.getFile("delete4.txt");
+ file.delete(false, DEFAULT_MONITOR);
+ deleteResources(project, new String[] {"delete5.txt"}, false);
+
+ // Checkout a copy and commit the deletion
+ copy = checkoutCopy(project, "-copy");
+ setContentsAndEnsureModified(copy.getFile("delete1.txt"));
+ setContentsAndEnsureModified(copy.getFile("delete2.txt"));
+ deleteResources(copy, new String[] {"delete3.txt", "delete4.txt", "delete5.txt"}, false);
+ commitProject(copy);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testDeletionConflictsB", project,
+ new String[] { "delete1.txt", "delete2.txt", "delete3.txt", "delete4.txt", "delete5.txt"},
+ true, new int[] {
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC });
+
+ // Release the resources
+ commitResources(project, new String[] { "delete1.txt", "delete2.txt", "delete3.txt", "delete4.txt", "delete5.txt"});
+
+ assertSyncEquals("testDeletionConflictsB", project,
+ new String[] { "delete3.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC });
+
+ assertDeleted("testDeletionConflictsB", project, new String[] {"delete1.txt", "delete2.txt", "delete4.txt", "delete5.txt"});
+ }
+
+ /*
+ * Test the creation and sync of an empty local project that has remote contents
+ */
+ public void testSyncOnEmptyProject() throws TeamException {
+ }
+
+ /*
+ * Test syncing on a folder that has been deleted from the server
+ */
+ public void testSyncOnDeletedFolder() throws TeamException {
+ }
+
+ /*
+ * Test syncing on a folder that is empty on the server and has been pruned, then added locally
+ */
+ public void testSyncOnPrunedFolder() throws TeamException {
+ }
+
+ /*
+ * Test sync involving pruned directories
+ */
+ public void testSyncWithPruning() throws TeamException {
+ }
+
+ /*
+ * Test a conflict with an incomming foler addition and an unmanaqged lcoal folder
+ */
+ public void testFolderConflict() throws TeamException, CoreException {
+
+ // Create a test project (which commits it as well) and delete the resource without committing
+ IProject project = createProject("testFolderConflict", new String[] { "file.txt"});
+
+ // Checkout a copy and add some folders
+ IProject copy = checkoutCopy(project, "-copy");
+ addResources(copy, new String[] {"folder1/file.txt", "folder2/file.txt"}, true);
+
+ // Add a folder to the original project (but not using cvs)
+ IResource[] resources = buildResources(project, new String[] {"folder1/"});
+ ((IFolder)resources[0]).create(false, true, DEFAULT_MONITOR);
+
+ assertSyncEquals("testFolderConflict", project,
+ new String[] { "file.txt", "folder1/", "folder1/file.txt", "folder2/", "folder2/file.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.CONFLICTING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION});
+
+ updateResources(
+ project,
+ new String[] {"folder1/"},
+ false /* ignore local changes */);
+
+ assertSyncEquals("testFolderConflict", project,
+ new String[] { "file.txt", "folder1/", "folder1/file.txt", "folder2/", "folder2/file.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION,
+ SyncInfo.INCOMING | SyncInfo.ADDITION});
+ }
+
+ /*
+ * Test that a deleted file can still be deleted through the team provider
+ */
+ public void testOutgoingDeletion() throws TeamException, CoreException {
+
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testOutgoingDeletion", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Delete a file
+ IFile file = project.getFile("folder1/b.txt");
+ file.delete(true, DEFAULT_MONITOR); // WARNING: As of 2002/03/05, this is equivalent to a cvs remove
+
+ // Get the sync tree for the project
+ assertSyncEquals("testOutgoingDeletion", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION});
+
+ // Commit the deletion
+ commitResources(project , new String[] {"folder1/b.txt"});
+
+ // Get the sync tree again for the project and ensure others aren't effected
+ assertSyncEquals("testOutgoingDeletion", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+
+ // Assert that deletion no longer appears in remote tree
+ assertDeleted("testOutgoingDeletion", project, new String[] {"folder1/b.txt"});
+ }
+
+ /*
+ * Test catching up to an incoming addition
+ */
+ public void testIncomingAddition() throws TeamException, CoreException {
+ // Create a test project
+ IProject project = createProject("testIncomingAddition", new String[] { "file1.txt", "folder1/", "folder1/a.txt"});
+
+ // Checkout and modify a copy
+ IProject copy = checkoutCopy(project, "-copy");
+ addResources(copy, new String[] { "folder1/add.txt" }, true);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testIncomingAddition", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.INCOMING | SyncInfo.ADDITION});
+
+ // Catch up to the addition by updating
+ updateResources(
+ project,
+ new String[] {"folder1/add.txt"},
+ false /* ignore local changes */);
+
+ // Get the sync tree again for the project and ensure the added resource is in sync
+ assertSyncEquals("testIncomingAddition", project,
+ new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/add.txt"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC});
+ }
+
+ /*
+ * Test changes using a granularity of contents
+ */
+// public void testGranularityContents() throws TeamException, CoreException, IOException {
+// // Create a test project (which commits it as well)
+// IProject project = createProject("testGranularityContents", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+//
+// // Checkout a copy and make some modifications
+// IProject copy = checkoutCopy(project, "-copy");
+// appendText(copy.getFile("file1.txt"), "same text", false);
+// setContentsAndEnsureModified(copy.getFile("folder1/a.txt"));
+// commitProject(copy);
+//
+// // Make the same modifications to the original
+// appendText(project.getFile("file1.txt"), "same text", false);
+// setContentsAndEnsureModified(project.getFile("folder1/a.txt"), "unique text");
+//
+// // Get the sync tree for the project
+// String oldId = getSubscriber().getCurrentComparisonCriteria().getId();
+// // TODO: There should be a better way to handle the selection of comparison criteria
+// getSubscriber().setCurrentComparisonCriteria("org.eclipse.team.comparisoncriteria.content");
+// assertSyncEquals("testGranularityContents", project,
+// new String[] { "file1.txt", "folder1/", "folder1/a.txt"},
+// true, new int[] {
+// SyncInfo.IN_SYNC,
+// SyncInfo.IN_SYNC,
+// SyncInfo.CONFLICTING | SyncInfo.CHANGE });
+// getSubscriber().setCurrentComparisonCriteria(oldId);
+//
+// }
+
+// public void testSimpleMerge() throws TeamException, CoreException, IOException {
+// // Create a test project (which commits it as well)
+// IProject project = createProject("testSimpleMerge", new String[] { "file1.txt", "file2.txt", "file3.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+//
+// // Checkout and modify a copy
+// IProject copy = checkoutCopy(project, "-copy");
+// copy.refreshLocal(IResource.DEPTH_INFINITE, DEFAULT_MONITOR);
+//
+// tagProject(project, new CVSTag("v1", CVSTag.VERSION));
+// tagProject(project, new CVSTag("branch1", CVSTag.BRANCH));
+//
+// getProvider(copy).update(new IResource[] {copy}, Command.NO_LOCAL_OPTIONS,
+// new CVSTag("branch1", CVSTag.BRANCH), true /*createBackups*/, DEFAULT_MONITOR);
+//
+// // make changes on the branch
+// addResources(copy, new String[] {"addition.txt", "folderAddition/", "folderAddition/new.txt"}, true);
+// deleteResources(copy, new String[] {"folder1/b.txt"}, true);
+// changeResources(copy, new String[] {"file1.txt", "file2.txt"}, true);
+//
+// // make change to workspace working on HEAD
+// changeResources(project, new String[] {"file2.txt"}, false);
+// changeResources(project, new String[] {"file3.txt"}, true);
+//
+// IRemoteResource base = CVSWorkspaceRoot.getRemoteTree(project, new CVSTag("v1", CVSTag.VERSION), DEFAULT_MONITOR);
+// IRemoteResource remote = CVSWorkspaceRoot.getRemoteTree(project, new CVSTag("branch1", CVSTag.BRANCH), DEFAULT_MONITOR);
+// SyncInfo tree = new CVSRemoteSyncElement(true /*three way*/, project, base, remote);
+//
+// // watch for empty directories and the prune option!!!
+// assertSyncEquals("testSimpleMerge sync check", tree,
+// new String[] { "addition.txt", "folderAddition/", "folderAddition/new.txt",
+// "folder1/b.txt", "file1.txt", "file2.txt", "file3.txt"},
+// new int[] { SyncInfo.INCOMING | SyncInfo.ADDITION,
+// SyncInfo.INCOMING | SyncInfo.ADDITION,
+// SyncInfo.INCOMING | SyncInfo.ADDITION,
+// SyncInfo.INCOMING | SyncInfo.DELETION,
+// SyncInfo.INCOMING | SyncInfo.CHANGE,
+// SyncInfo.CONFLICTING | SyncInfo.CHANGE,
+// SyncInfo.OUTGOING | SyncInfo.CHANGE });
+// }
+//
+// public void testSyncOnBranch() throws TeamException, CoreException, IOException {
+//
+// // Create a test project and a branch
+// IProject project = createProject("testSyncOnBranch", new String[] { "file1.txt", "file2.txt", "file3.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+// CVSTag branch = new CVSTag("branch1", CVSTag.BRANCH);
+// tagProject(project, branch);
+// getProvider(project).update(new IResource[] {project}, Command.NO_LOCAL_OPTIONS, branch, true /*createBackups*/, DEFAULT_MONITOR);
+//
+// // Checkout and modify a copy
+// IProject copy = checkoutCopy(project, branch);
+// addResources(copy, new String[] {"addition.txt", "folderAddition/", "folderAddition/new.txt"}, true);
+// deleteResources(copy, new String[] {"folder1/b.txt"}, true);
+// changeResources(copy, new String[] {"file1.txt", "file2.txt"}, true);
+//
+// // Sync on the original and assert the result equals the copy
+// SyncInfo tree = CVSWorkspaceRoot.getRemoteSyncTree(project, null, DEFAULT_MONITOR);
+// assertEquals(Path.EMPTY, (ICVSResource)tree.getRemote(), CVSWorkspaceRoot.getCVSResourceFor(copy), false, false);
+// }
+
+ public void testRenameProject() throws TeamException, CoreException, IOException {
+ String[] resourceNames = new String[] { "changed.txt", "folder1/", "folder1/a.txt" };
+ int[] inSync = new int[] {SyncInfo.IN_SYNC, SyncInfo.IN_SYNC, SyncInfo.IN_SYNC};
+ IProject project = createProject("testRenameProject", new String[] { "changed.txt", "folder1/", "folder1/a.txt" });
+
+ assertSyncEquals("sync should be in sync", project, resourceNames, true, inSync);
+ IProjectDescription desc = project.getDescription();
+ String newName = project.getName() + "_renamed";
+ desc.setName(newName);
+ project.move(desc, false, null);
+ project = ResourcesPlugin.getWorkspace().getRoot().getProject(newName);
+ assertTrue(project.exists());
+ assertSyncEquals("sync should be in sync", project, resourceNames, true, inSync);
+ }
+
+ public void testFolderDeletion() throws TeamException, CoreException {
+
+ IProject project = createProject("testFolderDeletion", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt", "folder1/folder2/file.txt"});
+
+ // Delete a folder and ensure that the file is managed but doesn't exist
+ // (Special behavior is provider by the CVS move/delete hook but this is not part of CVS core)
+ project.getFolder("folder1").delete(false, false, null);
+ ICVSFolder folder = CVSWorkspaceRoot.getCVSFolderFor(project.getFolder("folder1"));
+ assertTrue("Deleted folder not in proper state", ! folder.exists() && folder.isManaged() && folder.isCVSFolder());
+
+ // The files should show up as outgoing deletions
+ assertSyncEquals("testFolderDeletion sync check", project,
+ new String[] { "folder1/", "folder1/a.txt", "folder1/folder2/", "folder1/folder2/file.txt"},
+ true, new int[] { SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION});
+
+ // commit folder1/a.txt
+ commitResources(project, new String[] { "folder1/a.txt" });
+
+ // Resync and verify that above file is gone and others remain the same
+ assertSyncEquals("testFolderDeletion sync check", project,
+ new String[] { "folder1/", "folder1/folder2/", "folder1/folder2/file.txt"},
+ true, new int[] { SyncInfo.IN_SYNC,
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.DELETION});
+ assertDeleted("testFolderDeletion", project, new String[] {"folder1/a.txt"});
+
+ // Commit folder1/folder2/file.txt
+ commitResources(project, new String[] { "folder1/folder2/file.txt" });
+
+ // Resync and verify that all are deleted
+ assertDeleted("testFolderDeletion", project, new String[] {"folder1/", "folder1/folder2/", "folder1/folder2/file.txt"});
+ }
+ /**
+ * There is special handling required when building a sync tree for a tag when there are undiscovered folders
+ * that only contain other folders.
+ */
+ public void testTagRetrievalForFolderWithNoFile() throws TeamException, CoreException {
+ IProject project = createProject("testTagRetrievalForFolderWithNoFile", new String[] { "changed.txt", "deleted.txt", "folder1/", "folder1/a.txt"});
+ // Checkout, branch and modify a copy
+ IProject copy = checkoutCopy(project, "-copy");
+ CVSTag version = new CVSTag("v1", CVSTag.BRANCH);
+ CVSTag branch = new CVSTag("branch1", CVSTag.BRANCH);
+ getProvider(copy).makeBranch(new IResource[] {copy}, version, branch, true, DEFAULT_MONITOR);
+ addResources(copy, new String[] {"folder2/folder3/a.txt"}, true);
+
+ // Fetch the tree corresponding to the branch using the original as the base.
+ // XXX This will fail for CVSNT with directory pruning on
+ refresh(getSubscriber(), project);
+ }
+
+ public void testIgnoredResource() throws CoreException, TeamException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testIgnoredResource", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Create a new file without adding it to version control
+ buildResources(project, new String[] {"ignored.txt"}, false);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testIgnoredResource", project,
+ new String[] { "ignored.txt"},
+ true, new int[] {SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ IFile ignores = project.getFile(".cvsignore");
+ ignores.create(new ByteArrayInputStream("ignored.txt".getBytes()), false, DEFAULT_MONITOR);
+ addResources(new IResource[] {ignores});
+
+ assertSyncEquals("testIgnoredResource", project,
+ new String[] { "ignored.txt", ".cvsignore"},
+ true, new int[] {
+ SyncInfo.IN_SYNC,
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+ }
+
+ public void testRenameUnshared() throws CoreException, TeamException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testRenameUnshared", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Create a new file without adding it to version control
+ buildResources(project, new String[] {"oldName.txt"}, false);
+
+ // Get the sync tree for the project
+ assertSyncEquals("testRenameUnshared", project,
+ new String[] { "oldName.txt" },
+ true, new int[] {SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ IFile rename = project.getFile("oldName.txt");
+ rename.move(new Path("newName.txt"), false, false, DEFAULT_MONITOR);
+
+ assertDeleted("testRenameUnshared", project, new String[] {"oldName.txt"});
+
+ assertSyncEquals("testRenameUnshared", project,
+ new String[] { "newName.txt"},
+ true, new int[] {
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+ }
+
+ public void testOutgoingEmptyFolder() throws CoreException, TeamException {
+ // Create a test project (which commits it as well)
+ IProject project = createProject("testOutgoingEmptyFolder", new String[] { "file1.txt", "folder1/", "folder1/a.txt", "folder1/b.txt"});
+
+ // Create an empty folder without adding it to version control
+ buildResources(project, new String[] {"folder2/"}, false);
+
+ assertSyncEquals("testOutgoingEmptyFolder", project,
+ new String[] { "folder2/" },
+ true, new int[] {
+ SyncInfo.OUTGOING | SyncInfo.ADDITION});
+
+ commitResources(project, new String[] { "folder2" });
+
+ assertSyncEquals("testOutgoingEmptyFolder", project,
+ new String[] { "folder2/" },
+ true, new int[] {
+ SyncInfo.IN_SYNC});
+
+ // Ensure that the folder still exists (i.e. wasn't pruned)
+ assertTrue("Folder should still exist", project.getFolder("folder2").exists());
+ }
+
+}

Back to the top