blob: 5a06f58f5f007fa16faaa54de61825f94097f613 [file] [log] [blame]
package org.eclipse.team.internal.ccvs.core.resources;
/*
* (c) Copyright IBM Corp. 2000, 2002.
* All Rights Reserved.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
import org.eclipse.team.internal.ccvs.core.CVSStatus;
import org.eclipse.team.internal.ccvs.core.CVSTag;
import org.eclipse.team.internal.ccvs.core.ICVSFile;
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.client.Command;
import org.eclipse.team.internal.ccvs.core.client.Session;
import org.eclipse.team.internal.ccvs.core.client.Update;
import org.eclipse.team.internal.ccvs.core.client.Command.GlobalOption;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.Command.QuietOption;
import org.eclipse.team.internal.ccvs.core.client.listeners.IStatusListener;
import org.eclipse.team.internal.ccvs.core.client.listeners.IUpdateMessageListener;
import org.eclipse.team.internal.ccvs.core.client.listeners.StatusListener;
import org.eclipse.team.internal.ccvs.core.client.listeners.UpdateListener;
import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.MutableResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
/*
* This class is responsible for building a remote tree that shows the repository
* state of a locally loaded folder tree.
*
* It is used as follows
*
* RemoteFolderTreeBuilder.buildRemoteTree(CVSRepositoryLocation, IManagedFolder, String, IProgressMonitor);
*
* The provider IManagedFolder can be a local resource or a RemoteFolderTree that
* that was previously built.
*/
public class RemoteFolderTreeBuilder {
private static final int MAX_REVISION_FETCHES_PER_CONNECTION = 1024;
private Map fileDeltas;
private List changedFiles;
private Map remoteFolderTable;
private ICVSFolder root;
private RemoteFolderTree remoteRoot;
private CVSRepositoryLocation repository;
private CVSTag tag;
private LocalOption[] updateLocalOptions;
private boolean projectDoesNotExist = false;
private static String UNKNOWN = ""; //$NON-NLS-1$
private static String DELETED = "DELETED"; //$NON-NLS-1$
private static String ADDED = "ADDED"; //$NON-NLS-1$
private static String FOLDER = "FOLDER"; //$NON-NLS-1$
private static Map EMPTY_MAP = new HashMap();
static class DeltaNode {
int syncState = Update.STATE_NONE;
String name;
String revision;
DeltaNode(String name, String revision, int syncState) {
this.name = name;
this.revision = revision;
this.syncState = syncState;
}
String getName() {
return name;
}
String getRevision() {
return revision;
}
int getSyncState() {
return syncState;
}
}
private RemoteFolderTreeBuilder(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag) {
this.repository = repository;
this.root = root;
this.tag = tag;
this.fileDeltas = new HashMap();
this.changedFiles = new ArrayList();
this.remoteFolderTable = new HashMap();
// Build the local options
List localOptions = new ArrayList();
localOptions.add(Update.RETRIEVE_ABSENT_DIRECTORIES);
if (tag != null) {
if (tag.getType() == CVSTag.HEAD) {
localOptions.add(Update.CLEAR_STICKY);
} else {
localOptions.add(Update.makeTagOption(tag));
}
}
updateLocalOptions = (LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]);
}
private LocalOption[] getOptionsWithoutTag() {
// Build the local options
List localOptions = new ArrayList();
localOptions.add(Update.RETRIEVE_ABSENT_DIRECTORIES);
return (LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]);
}
public static RemoteFolderTree buildBaseTree(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag, IProgressMonitor progress) throws CVSException {
try {
RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, root, tag);
progress.beginTask(null, 100);
IProgressMonitor subProgress = Policy.infiniteSubMonitorFor(progress, 100);
subProgress.beginTask(null, 512); //$NON-NLS-1$
subProgress.subTask(Policy.bind("RemoteFolderTreeBuilder.buildingBase", root.getName())); //$NON-NLS-1$
return builder.buildBaseTree(null, root, subProgress);
} finally {
progress.done();
}
}
public static RemoteFolderTree buildRemoteTree(CVSRepositoryLocation repository, IContainer root, CVSTag tag, IProgressMonitor monitor) throws CVSException {
return buildRemoteTree(repository, CVSWorkspaceRoot.getCVSFolderFor(root), tag, monitor);
}
public static RemoteFolderTree buildRemoteTree(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag, IProgressMonitor monitor) throws CVSException {
RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, root, tag);
return builder.buildTree(monitor);
}
public static RemoteFile buildRemoteTree(CVSRepositoryLocation repository, ICVSFile file, CVSTag tag, IProgressMonitor monitor) throws CVSException {
RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, file.getParent(), tag);
return builder.buildTree(file, monitor);
}
private RemoteFolderTree buildTree(IProgressMonitor monitor) throws CVSException {
// Make sure that the cvs commands are not quiet during this operations
QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
try {
CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
monitor.beginTask(null, 100);
Policy.checkCanceled(monitor);
Session session = new Session(repository, root, false);
session.open(Policy.subMonitorFor(monitor, 10));
try {
Policy.checkCanceled(monitor);
fetchDelta(session, Session.CURRENT_LOCAL_FOLDER, Policy.subMonitorFor(monitor, 50));
if (projectDoesNotExist) {
// We cannot handle the case where a project (i.e. the top-most CVS folder)
// has been deleted directly on the sever (i.e. deleted using rm -rf)
if (root.isCVSFolder() && ! root.isManaged()) {
throw new CVSException(Policy.bind("RemoteFolderTreeBuild.folderDeletedFromServer", root.getFolderSyncInfo().getRepository()));
} else {
return null;
}
}
} finally {
session.close();
}
// We need a second session because of the use of a different handle on the same remote resource
// Perhaps we could support the changing of a sessions root as long as
// the folder sync info is the same
remoteRoot =
new RemoteFolderTree(null, root.getName(), repository,
new Path(root.getFolderSyncInfo().getRepository()),
tagForRemoteFolder(root, tag));
session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 10));
try {
// Set up an infinite progress monitor for the recursive build
IProgressMonitor subProgress = Policy.infiniteSubMonitorFor(monitor, 30);
subProgress.beginTask(null, 512);
// Build the remote tree
buildRemoteTree(session, root, remoteRoot, Path.EMPTY, subProgress);
// we can only fecth the status for up to 1024 files in a single connection due to
// the server which has a limit on the number of "open" files.
if (!changedFiles.isEmpty() && changedFiles.size() <= MAX_REVISION_FETCHES_PER_CONNECTION) {
fetchFileRevisions(session, (String[])changedFiles.toArray(new String[changedFiles.size()]), Policy.subMonitorFor(monitor, 20));
}
} finally {
session.close();
}
// If there were more than 1024 changed files, we need a connection per each 1024
if (!changedFiles.isEmpty() && changedFiles.size() > MAX_REVISION_FETCHES_PER_CONNECTION) {
String[] allChangedFiles = (String[])changedFiles.toArray(new String[changedFiles.size()]);
int iterations = (allChangedFiles.length / MAX_REVISION_FETCHES_PER_CONNECTION)
+ (allChangedFiles.length % MAX_REVISION_FETCHES_PER_CONNECTION == 0 ? 0 : 1);
for (int i = 0; i < iterations ; i++) {
int length = Math.min(MAX_REVISION_FETCHES_PER_CONNECTION,
allChangedFiles.length - (MAX_REVISION_FETCHES_PER_CONNECTION * i));
String buffer[] = new String[length];
System.arraycopy(allChangedFiles, i * MAX_REVISION_FETCHES_PER_CONNECTION, buffer, 0, length);
session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 1));
try {
fetchFileRevisions(session, buffer, Policy.subMonitorFor(monitor, 2));
} finally {
session.close();
}
}
}
return remoteRoot;
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
}
private RemoteFile buildTree(ICVSFile file, IProgressMonitor monitor) throws CVSException {
QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
try {
CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
monitor.beginTask(null, 100);
// Query the server to see if there is a delta available
Policy.checkCanceled(monitor);
Session session = new Session(repository, root, false);
session.open(Policy.subMonitorFor(monitor, 10));
try {
Policy.checkCanceled(monitor);
fetchDelta(session, file.getName(), Policy.subMonitorFor(monitor, 50));
if (projectDoesNotExist) {
return null;
}
} finally {
session.close();
}
// Create a parent for the remote resource
remoteRoot =
new RemoteFolderTree(null, root.getName(), repository,
new Path(root.getFolderSyncInfo().getRepository()),
tagForRemoteFolder(root, tag));
// Create the remote resource (using the delta if there is one)
RemoteFile remoteFile;
Map deltas = (Map)fileDeltas.get(Path.EMPTY);
if (deltas == null || deltas.isEmpty()) {
// If the file is an addition, return null as the remote
// Note: If there was a conflicting addition, the delta would not be empty
if ( ! file.isManaged() || file.getSyncInfo().isAdded()) {
return null;
}
remoteFile = new RemoteFile(remoteRoot, file.getSyncInfo());
} else {
DeltaNode d = (DeltaNode)deltas.get(file.getName());
if (d.getRevision() == DELETED) {
return null;
}
remoteFile = new RemoteFile(remoteRoot, d.getSyncState(), file.getName(), tagForRemoteFolder(remoteRoot, tag));
}
// Add the resource to its parent
remoteRoot.setChildren(new ICVSRemoteResource[] {remoteFile});
// If there was a delta, ftech the new revision
if (!changedFiles.isEmpty()) {
// Add the remote folder to the remote folder lookup table (used to update file revisions)
remoteFolderTable.put(new Path(remoteRoot.getFolderSyncInfo().getRemoteLocation()), remoteRoot);
session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 10));
try {
fetchFileRevisions(session, (String[])changedFiles.toArray(new String[changedFiles.size()]), Policy.subMonitorFor(monitor, 20));
} finally {
session.close();
}
}
return remoteFile;
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
}
/*
* Build the base remote tree from the local tree.
*
* The localPath is used to retrieve deltas from the recorded deltas
*
* Does 1 work for each managed file and folder
*/
private RemoteFolderTree buildBaseTree(RemoteFolderTree parent, ICVSFolder local, IProgressMonitor monitor) throws CVSException {
Policy.checkCanceled(monitor);
// Create a remote folder tree corresponding to the local resource
RemoteFolderTree remote = new RemoteFolderTree(parent, local.getName(), repository, new Path(local.getFolderSyncInfo().getRepository()), local.getFolderSyncInfo().getTag());
// Create a List to contain the created children
List children = new ArrayList();
// Build the child folders corresponding to local folders base
ICVSResource[] folders = local.members(ICVSFolder.FOLDER_MEMBERS);
for (int i=0;i<folders.length;i++) {
ICVSFolder folder = (ICVSFolder)folders[i];
if (folder.isManaged() && folder.isCVSFolder()) {
monitor.worked(1);
children.add(buildBaseTree(remote, folder, monitor));
}
}
// Build the child files corresponding to local files base
ICVSResource[] files = local.members(ICVSFolder.FILE_MEMBERS);
for (int i=0;i<files.length;i++) {
ICVSFile file = (ICVSFile)files[i];
ResourceSyncInfo info = file.getSyncInfo();
// if there is no sync info then there is no base
if (info==null)
continue;
// There is no remote if the file was added
if (info.isAdded())
continue;
// If the file was deleted locally, we need to generate a new sync info without the delete flag
if (info.isDeleted()) {
MutableResourceSyncInfo undeletedInfo = info.cloneMutable();
undeletedInfo.setDeleted(false);
info = undeletedInfo;
}
children.add(new RemoteFile(remote, info));
monitor.worked(1);
}
// Add the children to the remote folder tree
remote.setChildren((ICVSRemoteResource[])children.toArray(new ICVSRemoteResource[children.size()]));
return remote;
}
/*
* Build the remote tree from the local tree and the recorded deltas.
*
* The localPath is used to retrieve deltas from the recorded deltas
*
* Does 1 work for each file and folder delta processed
*/
private void buildRemoteTree(Session session, ICVSFolder local, RemoteFolderTree remote, IPath localPath, IProgressMonitor monitor) throws CVSException {
Policy.checkCanceled(monitor);
// Add the remote folder to the remote folder lookup table (used to update file revisions)
remoteFolderTable.put(new Path(remote.getFolderSyncInfo().getRemoteLocation()), remote);
// Create a map to contain the created children
Map children = new HashMap();
// If there's no corresponding local resource then we need to fetch its contents in order to populate the deltas
if (local == null) {
fetchNewDirectory(session, remote, localPath, monitor);
}
// Fetch the delta's for the folder
Map deltas = (Map)fileDeltas.get(localPath);
if (deltas == null)
deltas = EMPTY_MAP;
// If there is a local, use the local children to start buidling the remote children
if (local != null) {
// Build the child folders corresponding to local folders
ICVSResource[] folders = local.members(ICVSFolder.FOLDER_MEMBERS);
for (int i=0;i<folders.length;i++) {
ICVSFolder folder = (ICVSFolder)folders[i];
DeltaNode d = (DeltaNode)deltas.get(folder.getName());
if (folder.isCVSFolder() && ! isOrphanedSubtree(session, folder) && (d==null || d.getRevision() != DELETED)) {
children.put(folders[i].getName(),
new RemoteFolderTree(remote, folders[i].getName(), repository,
new Path(folder.getFolderSyncInfo().getRepository()),
tagForRemoteFolder(folder,tag)));
}
}
// Build the child files corresponding to local files
ICVSResource[] files = local.members(ICVSFolder.FILE_MEMBERS);
for (int i=0;i<files.length;i++) {
ICVSFile file = (ICVSFile)files[i];
DeltaNode d = (DeltaNode)deltas.get(file.getName());
ResourceSyncInfo info = file.getSyncInfo();
// if there is no sync info then there isn't a remote file for this local file on the
// server.
if (info==null)
continue;
// There is no remote if the file was added and we didn't get a conflict (C) indicator from the server
if (info.isAdded() && d==null)
continue;
// There is no remote if the file was deleted and we didn;t get a remove (R) indicator from the server
if (info.isDeleted() && d==null)
continue;
int type = d==null ? Update.STATE_NONE : d.getSyncState();
children.put(file.getName(), new RemoteFile(remote, type, info));
}
}
// Build the children for new or out-of-date resources from the deltas
Iterator i = deltas.keySet().iterator();
while (i.hasNext()) {
String name = (String)i.next();
DeltaNode d = (DeltaNode)deltas.get(name);
String revision = d.getRevision();
if (revision == FOLDER) {
// XXX should getRemotePath() return an IPath instead of a String?
children.put(name, new RemoteFolderTree(remote, repository,
new Path(remote.getRepositoryRelativePath()).append(name),
tagForRemoteFolder(remote, tag)));
} else if (revision == ADDED) {
children.put(name, new RemoteFile(remote, d.getSyncState(), name, tagForRemoteFolder(remote, tag)));
} else if (revision == UNKNOWN) {
// The local resource is out of sync with the remote.
// Create a RemoteFile associated with the tag so we are assured of getting the proper revision
// (Note: this will replace the RemoteFile added from the local base)
children.put(name, new RemoteFile(remote, d.getSyncState(), name, tagForRemoteFolder(remote, tag)));
} else if (revision == DELETED) {
// This should have been deleted while creating from the local resources.
// If it wasn't, delete it now.
if (children.containsKey(name))
children.remove(name);
} else {
// We should never get here
}
monitor.worked(1);
}
// Add the children to the remote folder tree
remote.setChildren((ICVSRemoteResource[])children.values().toArray(new ICVSRemoteResource[children.size()]));
// We have to delay building the child folders to support the proper fetching of new directories
// due to the fact that the same CVS home directory (i.e. the same root directory) must
// be used for all requests sent over the same connection
Iterator childIterator = children.entrySet().iterator();
List emptyChildren = new ArrayList();
while (childIterator.hasNext()) {
Map.Entry entry = (Map.Entry)childIterator.next();
if (((RemoteResource)entry.getValue()).isFolder()) {
RemoteFolderTree remoteFolder = (RemoteFolderTree)entry.getValue();
String name = (String)entry.getKey();
ICVSFolder localFolder;
DeltaNode d = (DeltaNode)deltas.get(name);
// for directories that are new on the server
if (d!=null && d.getRevision() == FOLDER)
localFolder = null;
else
localFolder = local.getFolder(name);
buildRemoteTree(session, localFolder, remoteFolder, localPath.append(name), monitor);
// Record any children that are empty
if (pruneEmptyDirectories() && remoteFolder.getChildren().length == 0) {
// Prune if the local folder is also empty.
if (localFolder == null || (localFolder.members(ICVSFolder.ALL_MEMBERS).length == 0))
emptyChildren.add(remoteFolder);
else {
// Also prune if the tag we are fetching is not HEAD and differs from the tag of the local folder
FolderSyncInfo info = localFolder.getFolderSyncInfo();
if (tag != null && info != null && ! tag.equals(CVSTag.DEFAULT) && ! tag.equals(info.getTag()))
emptyChildren.add(remoteFolder);
}
}
}
}
// Prune any empty child folders
if (pruneEmptyDirectories() && !emptyChildren.isEmpty()) {
List newChildren = new ArrayList();
newChildren.addAll(Arrays.asList(remote.getChildren()));
newChildren.removeAll(emptyChildren);
remote.setChildren((ICVSRemoteResource[])newChildren.toArray(new ICVSRemoteResource[newChildren.size()]));
}
}
/*
* This method fetches the delta between the local state and the remote state of the resource tree
* and records the deltas in the fileDeltas instance variable
*
* Returns the list of changed files
*/
private List fetchDelta(Session session, String argument, final IProgressMonitor monitor) throws CVSException {
// Create an listener that will accumulate new and removed files and folders
final List newChildDirectories = new ArrayList();
IUpdateMessageListener listener = new IUpdateMessageListener() {
public void directoryInformation(ICVSFolder root, IPath path, boolean newDirectory) {
if (newDirectory) {
// Record new directory with parent so it can be retrieved when building the parent
recordDelta(path, FOLDER, Update.STATE_NONE);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", path.toString())); //$NON-NLS-1$
// Record new directory to be used as a parameter to fetch its contents
newChildDirectories.add(path.toString());
}
}
public void directoryDoesNotExist(ICVSFolder root, IPath path) {
// Record removed directory with parent so it can be removed when building the parent
if (path.isEmpty()) {
projectDoesNotExist = true;
} else {
recordDelta(path, DELETED, Update.STATE_NONE);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", path.toString())); //$NON-NLS-1$
}
}
public void fileInformation(int type, ICVSFolder root, String filename) {
// Cases that do not require action are:
// case 'A' : = A locally added file that does not exists remotely
// case '?' : = A local file that has not been added and does not exists remotely
// case 'M' : = A locally modified file that has not been modified remotely
switch(type) {
case Update.STATE_MERGEABLE_CONFLICT :
case Update.STATE_CONFLICT :
// We have an remote change to a modified local file
// The change could be a local change conflicting with a remote deletion.
// If so, the deltas may already have a DELETED for the file.
// We shouldn't override this DELETED
IPath filePath = new Path(filename);
Map deltas = deltas = (Map)fileDeltas.get(filePath.removeLastSegments(1));
DeltaNode d = deltas != null ? (DeltaNode)deltas.get(filePath.lastSegment()) : null;
if ((d!=null) && (d.getRevision() == DELETED))
break;
case Update.STATE_DELETED : // We have a locally removed file that still exists remotely
case Update.STATE_REMOTE_CHANGES : // We have an remote change to an unmodified local file
changedFiles.add(filename);
recordDelta(new Path(filename), UNKNOWN, type);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", filename)); //$NON-NLS-1$
break;
}
}
public void fileDoesNotExist(ICVSFolder root, String filename) {
recordDelta(new Path(filename), DELETED, Update.STATE_NONE);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", filename)); //$NON-NLS-1$
}
};
// Perform a "cvs -n update -d [-r tag] ." in order to get the
// messages from the server that will indicate what has changed on the
// server.
IStatus status = Command.UPDATE.execute(session,
new GlobalOption[] { Command.DO_NOT_CHANGE },
updateLocalOptions,
new String[] { argument },
new UpdateListener(listener),
monitor);
return changedFiles;
}
/*
* Fetch the children of a previously unknown directory.
*
* The fetch may do up to 2 units of work in the provided monitor.
*/
private void fetchNewDirectory(Session session, RemoteFolderTree newFolder, IPath localPath, final IProgressMonitor monitor) throws CVSException {
// Create an listener that will accumulate new files and folders
IUpdateMessageListener listener = new IUpdateMessageListener() {
public void directoryInformation(ICVSFolder root, IPath path, boolean newDirectory) {
if (newDirectory) {
// Record new directory with parent so it can be retrieved when building the parent
// NOTE: Check path prefix
recordDelta(path, FOLDER, Update.STATE_NONE);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", path.toString())); //$NON-NLS-1$
}
}
public void directoryDoesNotExist(ICVSFolder root, IPath path) {
}
public void fileInformation(int type, ICVSFolder root, String filename) {
// NOTE: Check path prefix
changedFiles.add(filename);
recordDelta(new Path(filename), ADDED, type);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingDelta", filename)); //$NON-NLS-1$
}
public void fileDoesNotExist(ICVSFolder root, String filename) {
}
};
// NOTE: Should use the path relative to the remoteRoot
IPath path = new Path(newFolder.getRepositoryRelativePath());
IStatus status = Command.UPDATE.execute(session,
new GlobalOption[] { Command.DO_NOT_CHANGE },
updateLocalOptions,
new String[] { localPath.toString() },
new UpdateListener(listener),
Policy.subMonitorFor(monitor, 1));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
// FIXME: This should be refactored (maybe static methods on CVSException?)
CVSServerException e = new CVSServerException(status);
if ( ! e.isNoTagException() && e.containsErrors())
throw e;
// we now know that this is an exception caused by a cvs bug.
// if the folder has no files in it (just subfolders) cvs does not respond with the subfolders...
// workaround: retry the request with no tag to get the directory names (if any)
Policy.checkCanceled(monitor);
status = Command.UPDATE.execute(session,
new GlobalOption[] { Command.DO_NOT_CHANGE },
getOptionsWithoutTag(),
new String[] { localPath.toString() },
new UpdateListener(listener),
Policy.subMonitorFor(monitor, 1));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
}
}
// Get the file revisions for the given filenames
private void fetchFileRevisions(Session session, String[] fileNames, final IProgressMonitor monitor) throws CVSException {
// Create a listener for receiving the revision info
final Map revisions = new HashMap();
final List exceptions = new ArrayList();
IStatusListener listener = new IStatusListener() {
public void fileStatus(ICVSFolder root, IPath path, String remoteRevision) {
try {
updateRevision(path, remoteRevision);
monitor.subTask(Policy.bind("RemoteFolderTreeBuilder.receivingRevision", path.toString())); //$NON-NLS-1$
} catch (CVSException e) {
exceptions.add(e);
}
}
};
// Perform a "cvs status..." with a custom message handler
IStatus status = Command.STATUS.execute(session,
Command.NO_GLOBAL_OPTIONS,
Command.NO_LOCAL_OPTIONS,
fileNames,
new StatusListener(listener),
monitor);
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
// Report any exceptions that occured fecthing the revisions
if ( ! exceptions.isEmpty()) {
if (exceptions.size() == 1) {
throw (CVSException)exceptions.get(0);
} else {
MultiStatus multi = new MultiStatus(CVSProviderPlugin.ID, 0, Policy.bind("RemoteFolder.errorFetchingRevisions"), null); //$NON-NLS-1$
for (int i = 0; i < exceptions.size(); i++) {
multi.merge(((CVSException)exceptions.get(i)).getStatus());
}
throw new CVSException(multi);
}
}
}
private boolean pruneEmptyDirectories() {
return CVSProviderPlugin.getPlugin().getPruneEmptyDirectories();
}
/*
* Record the deltas in a double map where the outer key is the parent directory
* and the inner key is the file name. The value is the revision of the file or
* DELETED (file or folder). New folders have a revision of FOLDER.
*
* A revison of UNKNOWN indicates that the revision has not been fetched
* from the repository yet.
*/
private void recordDelta(IPath path, String revision, int syncState) {
IPath parent = path.removeLastSegments(1);
Map deltas = (Map)fileDeltas.get(parent);
if (deltas == null) {
deltas = new HashMap();
fileDeltas.put(parent, deltas);
}
String name = path.lastSegment();
deltas.put(name, new DeltaNode(name, revision, syncState));
}
private void updateRevision(IPath path, String revision) throws CVSException {
RemoteFolderTree folder = (RemoteFolderTree)remoteFolderTable.get(path.removeLastSegments(1));
if (folder == null) {
throw new CVSException(Policy.bind("RemoteFolderTreeBuilder.missingParent", path.toString(), revision));//$NON-NLS-1$
}
((RemoteFile)folder.getFile(path.lastSegment())).setRevision(revision);
}
/*
* Return the tag that should be associated with a remote folder.
*
* This method is used to ensure that new directories contain the tag
* derived from the parant local folder when appropriate. For instance,
*
* The tag should be the provided tag. However, if tag is null, the
* tag for the folder should be derived from the provided reference folder
* which could be the local resource corresponding to the remote or the parent
* of the remote.
*/
private CVSTag tagForRemoteFolder(ICVSFolder folder, CVSTag tag) throws CVSException {
return tag == null ? folder.getFolderSyncInfo().getTag() : tag;
}
private boolean isOrphanedSubtree(Session session, ICVSFolder mFolder) throws CVSException {
return mFolder.isCVSFolder() && ! mFolder.isManaged() && ! mFolder.equals(session.getLocalRoot()) && mFolder.getParent().isCVSFolder();
}
}