Resource Moves, Renames, and Deletes

Last revised 15:00 Thursday February 14, 2002

This proposal deals with improving the Eclipse Platform's support for allowing a Team provider to control the movement of existing resources under its control. The proposal would give Team providers a (headless) way of doing this sort of thing.

Work Item

Ref: uncommitted work item from early 2.0 plan.
Allow pre-validation of rename/move/delete. VCM providers that need to manage a project's namespace would like advance notification of impending resource moves, renames, and deletes. (Other clients would like a similar opportunity to veto inappropriate name changes to their resources; this is a different concern.) We will add a callback so that the relevant VCM provider will be able to register for advance notification with an opportunity to veto. These changes will affect the UI and Core components.
The title and description for this item is not quite right. The work item would be better described as:
Allow VCM control over rename/move/delete. Team providers sometimes need tighter control over how project resources are manipulated in the local file system. For instance, a project directory might be a specially mounted remote file system located on a Team server, and require special server communication in order to delete, move, or change the name of a resource. Or the Team provider may track version history across move/renames. (Other clients would like a similar opportunity to veto inappropriate name changes to their resources; this is a different concern.) We will add a headless callback so that the relevant Team provider will be able to control moves, renames, and deletes. These changes will affect the Core and Team components.

Problem

Some VCM systems must keep close tabs on the files under their control.

(Note that newly-created files are a separate matter; until they exist and have been placed under VCM control, changes to their names are not of interest. This is why create and copy operations are not included in this discussion.)

Background - support available in Eclipse 1.0

There is no support available in Eclipse 1.0 for any facet of this problem.

The workspace operations for moving resources are as follows:

The workspace operations for deleting resources are: All operations for moving and deleting resources are implemented entirely by the core resource plug-in; there is currently no mechanisms by which another party could participate.

Design Constraints

All of the operations for moving and deleting resources are available to arbitrary clients and are billed as "core" operations; i.e., operations that run headless. Callers may be holding locks at the time of call, or making the call in a non-UI thread. Consequently, it would be problematic if existing uses were to attempt to reacquire locks or pop up dialogs to interact with the user.

This means that none of the existing operations can ever bring up a UI - all must run headless. If we add hooks to these operations, the hooks must run headless.

The alternative is to add a parallel set of operations that would allow a UI-aware client to pass in a UI context in which to converse with the user (by employing exactly the same design as the recently-added validate-edit support). These new API methods could have more stringent contracts that spell out the conditions which UIs would need. However, this does nothing for existing plug-ins that manipulate resources with the exisiting API methods; these would still need to run headless.

Proposal

The move and delete operations have several facets: The proposal is that Core would provide a headless hook that would give the hook overall responsibility for carrying out these operations. The hook would have control over the local file system facet of the operation, and would call back into the Core to capture local file history, update the workspace resource tree, report progress, and report status back to the user. The hook would also be furnished with standard building block sub-operations, so that it is possible for a particular hook implementation to embellish the standard behavior without having to implement everything from scratch.

This hook would be filled by the Team component, which would delegate the request to the Team provider for the project that owns the resource. The Team provider would not be obliged to participate in these operations; if it decided to pass, the Core would fall back on its own internal implementation the operation much as it does currently.

Newly-created files and folders are not under version control automatically. Adding a file or folder to version control is a separate follow-on operation that each Team provider will give the user. This is why create and copy operations are not included in this hook.

Support in Core Component

Here is a sketch of how the move and delete resource operations in the API would be implemented (important details, such as batching, have been omitted in the interest of brevity):

Resource
   public IStatus move(
        IPath destination,
        boolean force,
        boolean keepHistory,
        IProgressMonitor monitor) throws CoreException {
    // do easy precondition checks before calling hook
    if (!this.exists()) throw new CoreException(...);
    if (findResource(destination) != null) throw new CoreException(...);
    IResourceTree rto = ...;
    INamespaceControlHook hook = ...;
    boolean done = false;
    if (hook != null) {
        done = hook.move(this, destination, force, keepHistory, monitor, rto);
    }
    if (!done) {
        internalMove(this, destination, force, keepHistory, monitor, rto);
    }
    if (rto.getStatus().isOK()) {
        return rto.getStatus();
    } else {
        throw new CoreException(rto.getStatus());
    }
}

IResource
   public IStatus delete(
        boolean force,
        boolean keepHistory,
        IProgressMonitor monitor) throws CoreException {
    // do easy precondition checks before calling hook
    if (!this.exists()) throw new CoreException(...);
    IResourceTree rto = ...;
    INamespaceControlHook hook = ...;
    boolean done = false;
    if (hook != null) {
        done = hook.delete(this, force, keepHistory, monitor, rto);
    }
    if (!done) {
        ops = internalDelete(this, force, keepHistory, monitor, rto);
    }
    if (rto.getStatus().isOK()) {
        return rto.getStatus();
    } else {
        throw new CoreException(rto.getStatus());
    }
}

(The API operations for moving or deleting a project have a slightly extended contract, but the general flavor of the hook is the same.)

Hook Semantics

The hook is bound by the terms of the API contract of the resource operation, including:

New Core API

The new core API is provided for the Team component and its Team providers. Rather than clutter up the main resources API package with this special-purpose API, we propose adding the new core API in a new org.eclipse.core.resources.team package. (The question of whether to use a separate API package is clearly a separable issue. If we do go for a separate package, we should consider moving the hook interface for validateEdit/validateSave into this package too.)

The INamespaceControlHook interface appears in the new core extension point, and would be implemented by the Team component, and likely by each Team provider.

package org.eclipse.core.resources.team
public interface INamespaceControlHook {
    public boolean delete(IResource resource,
            boolean force,
            boolean keepHistory,
            IProgressMonitor monitor,
            IResourceTree rto);
    public boolean move(IResource resource,
            IResource destination,
            boolean force,
            boolean keepHistory,
            IProgressMonitor monitor,
            IResourceTree rto);
    //... others for project move, project delete
}

The IResourceTree interface provides the operations that manipulate the resource tree (move markers, properties, collect result statuses) without touching the local file system. The sole implementation would be internal to core, and would be passed as a parameter to the INamespaceControlHook implementation.

package org.eclipse.core.resources.team
public interface IResourceTree {
    public void deletedFile(IResource file, IStatus status);
    public void deletedFolder(IResource folder, IStatus status);

    public void movedFile(IResource source, IResource dest,
        long destTimestamp, IStatus status);

    public void movedFolder(IResource source, IResource dest, IStatus status);
    public void movedEmptyFolder(IResource source, IResource dest, IStatus status);
    public void moved(IResource source, IResource dest);

    public void createdFile(IResource file, long timestamp, IStatus status);
    public void createdFolder(IResource folder, IStatus status);

    public boolean copyToLocalHistory(IResource file, boolean stealable);

    // standard building blocks
    public void standardDeleteResource(IResource resource,
        boolean force, boolean keepHistory, IProgressMonitor monitor);
    public void standardMoveResource(IResource resource, IResource dest,
        boolean force, boolean keepHistory, IProgressMonitor monitor);
    ...

    //... plus others for moving or deleting a project
}

New Core Extension Point

The Core resources plug-in would provide a new namespaceControl extension point intended to be extended by none other than the Team component.

Identifier: org.eclipse.core.resources.namespaceControl
Description: For providing an implementation of an INamespaceControlHook to be used in implementing resource moves and deletes. This extension point tolerates at most one extension.

Configuration Markup:

   <!ELEMENT namespaceControl EMPTY>
   <!ATTLIST namespaceControl class CDATA #REQUIRED>

class - a fully qualified name of a class which implements org.eclipse.core.resource.team.INamespaceControlHook.

Examples:
The following is an example of using the namespaceControl extension point.

(in file plugin.xml)

   <extension point="org.eclipse.core.resources.namespaceControl">
      <namespaceControl class="org.eclipse.team.internal.VCMNamespaceControlHook"/>
   </extension>

API Compatibility

In summary, the changes affect the Eclipse Platform Core component API in the following ways: The changes maintain full API compatibility with existing plug-ins in compliance with the 1.0 API.

Local History

The hook implementation calls copyToLocalHistory when appropriate to copy the state of a file to the local history before it is changed (or deleted). Bearing in mind that the purpose of the local history is to preserve intermediate file states so that the user has some options for recovering from mistakes, etc., the Team provider should consider whether anything is at risk before adding states to the local history. For instance, the state of a file that the user has neither checked out nor modified is likely not at risk since it exists as a version in the repository; deleting such a file would not deserve a local history entry.

Resource Tree Operations

The hook may need to access the workspace resource tree as well as the local file system. Since the hook is in the midst of a workspace operation, they must not call any of the normal resource API operations that modify resources. However, they must be able to at least walk the resource tree. This they should be able to do through the resource API.

In order to modify the resource tree, they call IResourceTree methods. These operations affect the resource tree (immediately). If the hook passes the timestamp for a file, it is used; if no timestamp is passed, the local file system is queried for the value.

Timestamping

The Core and the hook must employ the same file timestamp scheme; otherwise, the core will fail to correctly identify when files in the local file system change. Currently, the Core uses an internal native mechanism that is more accurate than java.io.File.lastModified(). We should review whether this is still needed; if it is, we need to add an API method (to IResourceTree) so that the Team provider can use it.

Multiple API Entry Points

There are several move methods in the API. All single resource moves should funnel through a single move operation taking a source resource, a target resource, a force flag, a keepHistory flag, and a progress monitor. IWorkspace.move moves multiple siblings to a single target folder. These would be passed through the move pinch point one at a time.

Likewise for delete methods. All single resource delete should funnel through a single delete operation taking a source resource, a force flag, a keepHistory flag, and a progress monitor. IWorkspace.delete deletes multiple independent resources, and would pass them through the delete pinch point one at a time. Resources scattered across different projects will get routed automatically to the appropriate project-specific Team provider.

Inter-project Moves

A move operation may move resources from one project into a different project. The move request will be dispatched to the Team provider of the project containing the source resource. The Team provider may decide to be clever if it also manages the destination project; otherwise, it should just use local file system copy operation to copy the files to the destination, and then delete the originals when done.

Note that each project in the workspace could have a different Team provider (or none at all), and each could map the project root directory to a different drive or OS file system.

Projects

The API operations for moving or deleting a project have a slightly extended contract, but the general flavor of the hook is the same. The hook is responsible for the files in the project's root directory in the local file system. Project properties, markers, and other project metadata are still a Core responsibility.

Other Useful Building Blocks

The IResourceTree API should also include standard implementations of the operations as additional building blocks. This would make it simpler for a Team provider to embellish the standard behavior instead of having to reimplement it from scratch.

Example

Assumptions:

Sequence of calls:

Folder[/P/f].move(dest=Path[/P/g],force=false,keepHistory=true,monitor)
- VCMNSHook.move(source=Folder[/P/f],dest=Folder[/P/g],force,keepHistory,monitor,rto)
-- VCMProvider1.move(source,dest,force,keepHistory,monitor,rto)
--- java.io creates folder /P/g
--- rto.createdFolder(dest, OK)
---- Folder[/P/g] created in resource tree
--- rto.copyToLocalHistory(File[P/f/a.txt],steal=false)
--- check timestamp on /P/f/a.txt is t1
--- java.io rename file /P/f/a.txt to /P/g/a.txt
--- new timestamp of /P/g/a.txt is t1'
--- rto.movedFile(File[/P/f/a.txt], t1, File[/P/g/a.txt], t1', OK)
---- File[/P/g/a.txt] created in resource tree with timestamp t1'
---- markers and properties copied from File[/P/f/a.txt] to File[/P/g/a.txt]
---- File[/P/f/a.txt] deleted from resource tree
---- delta for File[/P/g/a.txt] records moved from File[/P/f/a.txt]
---- delta for File[/P/f/a.txt] records moved to File[/P/g/a.txt]
--- check timestamp on /P/f/b.txt is t2
--- java.io rename file /P/f/b.txt to /P/g/b.txt
--- new timestamp of /P/g/b.txt is t2'
--- rto.movedFile(File[/P/f/b.txt], t2, File[/P/g/b.txt], t2', OK)
---- File[/P/g/b.txt] created in resource tree with timestamp t2'
---- markers and properties copied from File[/P/f/b.txt] to File[/P/g/b.txt]
---- File[/P/f/b.txt] deleted from resource tree
---- delta for File[/P/g/b.txt] records moved from File[/P/f/b.txt]
---- delta for File[/P/f/b.txt] records moved to File[/P/g/b.txt]
--- java.io deletes folder /P/f
--- rto.moved(source,dest)
---- markers and properties copied from Folder[/P/f] to Folder[/P/g]
---- delta for Folder[/P/g] records moved from Folder[/P/f]
---- delta for Folder[/P/f] records moved to Folder[/P/g]
--- return true
-- return true
- return OK

Support in Team Component

Support in Other Components

None required. These hooks are activated whenever a client calls a core API operation to delete, move, or rename a file or folder.