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:

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:

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.