Synchronization Support
Eclipse includes APIs for managing and displaying synchronization state
between workspace resources and resources in another location. We refer to a
resource outside of the workspace as a variant. Synchronizing is the act of
displaying the changes between resources in different locations and optionally
allowing the user to affect the synchronization state by performing an action.
The synchronize APIs are orthogonal to the RepositoryProvider APIs and can be
used without a repository provider. The purpose of the synchronization API is
to ease the task of implementing different ways of presenting the synchronization
state of resources. As such, the API requires a means to query the synchronization
state of resources but does not require a means to affect the state. The means
of affecting the state is left to the implementer (although the UI does provide
hooks for adding provider specific menu items to menus).
Terminology
Before the synchronization API is described, it is helpful to
present some of the terminology and concepts that apply when discussing
workspace synchronization.
Resource Variant: A local resource that is mapped to a resource
that exists at another location can be referred to as a variant of that resource.
That is, the resources are usually very similar but may differ slightly (either
due to modifications to the local resource or changes made the remote copy
by other users). We take a local workspace centric view of this, referring
to the local copy as the resource and any remote copy as resource variants.
Synchronize: We refer to synchronize as the action of displaying
to the user the differences between resource variants. Synchronizing doesn't
affect the state of the variants, but instead provides a view to help the
user understand the differences between different sets of variants. It is
common however to allow users to affect the states of the variants (e.g. allowing
to check-in, or revert) while synchronizing.
Two-way vs. Three-way Synchronization: There are two basic types
of synchronization state determination: two-way and three-way. A two-way comparison
only considers the local resource and a single resource variant, referred
to as the remote resource variant. This type of comparison can only show the
differences between the two resources but cannot offer hints as to how the
changes interrelate. Most code repository systems support a three-way comparison
for synchronization state determination. This type of comparison involves
the local resource, a remote resource variant and a base resource variant.
The base resource variant represents a common ancestor for the local and remote
resources. This allows for more sophisticated synchronization states that
indicate the direction of the change.
Table 1: The synchronization states
Two-Way
|
Three-Way
|
Changed
Deleted
Added |
Outgoing Change
Incoming Change
Outgoing Deletion
Incoming Deletion
Outgoing Addition
Incoming Addition
Conflicting Change
Conflicting Deletion
Conflicting Addition |
The Basics - SyncInfo
The classes in the org.eclipse.team.core.synchronize
are used to describe the synchronization state. The most important
class is SyncInfo
because it is the class that actually defines the synchronization
state. It can be used as follows:
SyncInfo info = getSyncInfo(resource); // this is a simulated method of obtaining the sync info for a resource
int changekind = info.getKind();
if(info.getResourceComparator().isThreeWay()) {
if((changeKind & SyncInfo.DIRECTION_MASK) == SyncInfo.INCOMING) {
// do something
}
} else if(changeKind == SyncInfo.CHANGE) {
// do something else
}
The SyncInfo class provides both the two-way and three-way comparison algorithms,
a client must provide the resources and a class that can compare the resources
(IResourceVariantComparator).
Here is an example variant comparator:
public class TimestampVariantComparator implements IResourceVariantComparator {
protected boolean compare(IResourceVariant e1, IResourceVariant e2) {
if(e1.isContainer()) {
if(e2.isContainer()) {
return true;
}
return false;
}
if(e1 instanceof MyResourceVariant && e2 instanceof MyResourceVariant) {
MyResourceVariant myE1 = (MyResourceVariant)e1;
MyResourceVariant myE2 = (MyResourceVariant)e2;
return myE1.getTimestamp().equals(myE2.getTimestamp());
}
return false;
}
protected boolean compare(IResource e1, IResourceVariant e2) {
}
public boolean isThreeWay() {
return true;
}
}
SyncInfo info = new SyncInfo(resource, variant1, variant2, new TimestampComparator());
info.init(); // calculate the sync info
This package also contains collections specifically designed to contain
SyncInfo and filters that can be applied to SyncInfo instances.
Managing the synchronization state
As we have seen in the examples above, SyncInfo
and
IResourceVariantComparator
classes provide access to the
synchronization state of resources. But what we haven't seen yet is how
the state is managed. A Subscriber
provides access to the synchronization state between the resources in
the local workspace and a set of resource variants for these resources
using either a two-way or three-way comparison, depending on the nature
of the subscriber. A subscriber provides the following capabilities:
-
local workspace traversal: a subscriber
supports the traversal of the local workspace resources that are supervised
by the subscriber. As such, the subscriber has a set of root resources
that define the workspace subtrees under the subscriber's control, as well
as a members method that returns the supervised members of a workspace
resource. This traversal differs from the usual workspace resource traversal
in that the resources being traversed may include resources that do not exist
locally, either because they have been deleted by the user locally or created
by a 3rd party.
-
resource synchronization state determination:
For supervised resources, the subscriber provides access to the synchronization
state of the resource, including access to the variants of the resource. For
each supervised resource, the subscriber provides a SyncInfo object
that contains the synchronization state and the variants used to
determine the state.The subscriber also provides an IResourceVariantComparator
which determines whether two-way or three-way comparison is to be used and
provides the logic used by the SyncInfo to comparing resource variants when
determining the synchronization state.
-
refresh of synchronization state and change
notification: Clients can react to changes that happen to local resources
by listening to the Core resource deltas. When a local resource is changed,
the synchronization state of the resource can then be re-obtained from the
subscriber. However, clients must explicitly query the server to know if there
are changes to the resource variants. For subscribers, this process is broken
up into two parts. A client can explicitly refresh a subscriber.
In response the subscriber will obtain the latest state of the resource variants
from the remote location and fire synchronization state change events
for any resource variants that have changed. The change notification is separate
from the refresh since there may be other operations that contact the remote
location and obtain the latest remote state.
The APIs do not not define how a Subscriber is created, this is left to the
specific implementations. For example the CVS plug-in creates a Subscriber when
a merge is performed, another for a comparison, and another when synchronizing
the local workspace with the current branch.
So let's revisit our first example of using SyncInfo and see how a
Subscriber could be used to access SyncInfo.
// Create a file system subscriber and specify that the
// subscriber will synchronize with the provided file system location
Subscriber subscriber = new FileSystemSubscriber("c:\temp\repo");
// Allow the subscriber to refresh its state
subscriber.refresh(subscriber.roots(), IResource.DEPTH_INFINITE, monitor);
// Collect all the synchronization states and print
IResource[] children = subscriber.roots();
for(int i=0; i < children.length; i++) {
printSyncState(children[i]);
}
...
void printSyncState(Subscriber subscriber, IResource resource) {
System.out.println(subscriber.getSyncInfo(resource).toString());
IResource[] children = subscriber.members(resource);
for(int i=0; i < children.length; i++) {
IResource child = children[i];
if(! child.exists()) {
System.out.println(resource.getFullPath() + " doesn't exist in the workspace");
}
printSyncState(subscriber, children[i]);
}
}
The important point to remember is that the Subscriber knows about
resources that do not exist in the workspace and non-existing resources
can be returned from the Subscriber#members()
and SyncInfo#getLocal().
Displaying the synchronizations state in the UI
We could spend more time explaining how to manage synchronization state
but instead let's see how to actually get the state shown to the user.
A ISynchronizeParticipant
is the user interface component that displays synchronization state
and allows the user to affect its state. The Synchronize View displays
synchronize participants, but it is also possible to show these in
dialogs and wizards. In order to provide support for users to show any
type of synchronization state to the user, even those not based on
SyncInfo and Subscribers, a participant is a very generic component.
There is also an extension point called org.eclipse.team.ui.synchronizeWizards
to add a synchronization creation wizard. This will put your wizard in
the global synchronize action and in the Synchronize View, so that
users can easily create a synchronization of your type.
However, if you have implemented a Subscriber you can benefit from a
concrete participant called SubscriberParticipant
which will provide
the following functionality:
- Collects SyncInfo from a Subscriber in the background.
- Listens to changes in the workspace and those found when a
Subscriber is refreshed and keeps the synchronization state updated
dynamically.
- Provides the user interface that support modes for filtering the
changes, and layouts.
- Support scheduling a refresh with the Subscriber so that the
synchronization states are kept up-to-date.
- Supports refreshing a Subscriber in the background.
- Supports navigation of the changes and showing the differences
between the files.
- Supports configuration of the actions, toolbars, and decorators
by subclasses.
The best way to explain these concepts are to see them used in the context
of a simple example. Go to the local history synchronization
example to see how all of these pieces can be used together. Or if you want
pointers on how to use the more advanced APIs, go to Beyond The Basics.