blob: 6ff79882a764736a9bbd422594266697d7637412 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core.resources;
import java.util.*;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.*;
import org.eclipse.team.internal.ccvs.core.client.listeners.*;
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.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.Util;
/*
* 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<String, Map> fileDeltas;
private List<String> changedFiles;
private Map<String, RemoteFolderTree> remoteFolderTable;
private ICVSFolder root;
private RemoteFolderTree remoteRoot;
private CVSRepositoryLocation repository;
private CVSTag tag;
private LocalOption[] updateLocalOptions;
private boolean rootDoesNotExist = 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();
private boolean newFolderExist = false;
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;
}
}
/* package */ RemoteFolderTreeBuilder(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag) {
this.repository = repository;
this.root = root;
this.tag = tag;
this.fileDeltas = new HashMap<String, Map>();
this.changedFiles = new ArrayList<String>();
this.remoteFolderTable = new HashMap<String, RemoteFolderTree>();
// Build the local options
List<LocalOption> localOptions = new ArrayList<>();
if (tag != null) {
if (tag.getType() == CVSTag.HEAD) {
localOptions.add(Update.CLEAR_STICKY);
} else {
localOptions.add(Update.makeTagOption(tag));
}
}
updateLocalOptions = localOptions.toArray(new LocalOption[localOptions.size()]);
}
private LocalOption[] getOptionsWithoutTag() {
// Build the local options
List<LocalOption> localOptions = new ArrayList<>();
localOptions.add(Update.RETRIEVE_ABSENT_DIRECTORIES);
return localOptions.toArray(new LocalOption[localOptions.size()]);
}
public static RemoteFolder 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);
subProgress.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_buildingBase, new String[] { root.getName() }));
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(new ICVSResource[] { root }, 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);
}
/* package */ RemoteFolderTree buildTree(ICVSResource[] resources, 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);
// 1st Connection: Use local state to determine delta with server
if (!fetchDelta(resources, Policy.subMonitorFor(monitor, 75))) {
return null;
}
// 2nd Connection: Build remote tree from above delta using 2nd connection to fetch unknown directories
// NOTE: Multiple commands may be issued over this connection.
fetchNewDirectories(Policy.subMonitorFor(monitor, 10));
// 3rd+ Connection: Used to fetch file status in groups of 1024
fetchFileRevisions(Policy.subMonitorFor(monitor, 15));
return remoteRoot;
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
}
private boolean fetchDelta(ICVSResource[] resources, IProgressMonitor monitor) throws CVSException {
// Get the arguments from the files
ArrayList<String> arguments = new ArrayList<>();
for (int i = 0; i < resources.length; i++) {
ICVSResource resource = resources[i];
arguments.add(resource.getRelativePath(root));
}
// Use local state to determine delta with server
monitor.beginTask(null, 100);
Policy.checkCanceled(monitor);
Session session = new Session(repository, root, false);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
Policy.checkCanceled(monitor);
fetchDelta(session, arguments.toArray(new String[arguments.size()]), Policy.subMonitorFor(monitor, 90));
if (rootDoesNotExist) {
// 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()) {
IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuild_folderDeletedFromServer, new String[] { root.getFolderSyncInfo().getRepository() }),root);
throw new CVSException(status);
} else {
return false;
}
}
} finally {
session.close();
monitor.done();
}
return true;
}
private void fetchNewDirectories(IProgressMonitor monitor) throws CVSException {
// Build remote tree from the fetched delta using a new connection to fetch unknown directories
// NOTE: Multiple commands may be issued over this connection.
monitor.beginTask(null, 100);
Session session;
FolderSyncInfo folderSyncInfo = root.getFolderSyncInfo();
if (folderSyncInfo == null) {
// We've lost the mapping in the local workspace.
// This could be due to the project being deleted.
if (root.exists()) {
IResource resource = root.getIResource();
String path;
if (resource == null) {
path = root.getName();
} else {
path = resource.getFullPath().toString();
}
IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuilder_0, new String[] { path }), root);
throw new CVSException(status);
} else {
// Just return. The remote tree will be null
return;
}
}
remoteRoot =
new RemoteFolderTree(null, root.getName(), repository,
folderSyncInfo.getRepository(),
tagForRemoteFolder(root, tag));
if (newFolderExist) {
// New folders will require a connection for fetching their members
session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
} else {
session = null;
}
try {
// Set up an infinite progress monitor for the recursive build
IProgressMonitor subProgress = Policy.infiniteSubMonitorFor(monitor, 90);
subProgress.beginTask(null, 512);
// Build the remote tree
buildRemoteTree(session, root, remoteRoot, "", subProgress); //$NON-NLS-1$
} finally {
if (session != null) {
session.close();
}
monitor.done();
}
}
private void fetchFileRevisions(IProgressMonitor monitor) throws CVSException {
// 3rd+ Connection: Used to fetch file status in groups of 1024
if (remoteRoot != null && !changedFiles.isEmpty()) {
String[] allChangedFiles = 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 session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 1), false /* read-only */);
try {
fetchFileRevisions(session, buffer, Policy.subMonitorFor(monitor, 2));
} finally {
session.close();
}
}
}
}
/* package */ 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), false /* read-only */);
try {
Policy.checkCanceled(monitor);
fetchDelta(session, new String[] { file.getName() }, Policy.subMonitorFor(monitor, 50));
if (rootDoesNotExist) {
return null;
}
} finally {
session.close();
}
// Create a parent for the remote resource
remoteRoot =
new RemoteFolderTree(null, root.getName(), repository,
root.getFolderSyncInfo().getRepository(),
tagForRemoteFolder(root, tag));
// Create the remote resource (using the delta if there is one)
RemoteFile remoteFile;
Map deltas = fileDeltas.get(""); //$NON-NLS-1$
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
byte[] syncBytes = file.getSyncBytes();
if ( syncBytes == null || ResourceSyncInfo.isAddition(syncBytes)) {
return null;
}
remoteFile = new RemoteFile(remoteRoot, syncBytes);
} else {
DeltaNode d = (DeltaNode)deltas.get(file.getName());
if (d.getRevision() == DELETED) {
return null;
}
CVSTag newTag = tagForRemoteFolder(remoteRoot, tag);
if (newTag == null && file.getSyncInfo() != null) {
newTag = file.getSyncInfo().getTag();
}
remoteFile = new RemoteFile(remoteRoot,
d.getSyncState(),
file.getName(),
null, /* the revision will be retrieved from the server */
getKeywordMode(file), /* use the same keyword mode as the local file */
newTag);
}
// Add the resource to its parent
remoteRoot.setChildren(new ICVSRemoteResource[] {remoteFile});
// If there was a delta, fetch the new revision
if (!changedFiles.isEmpty()) {
// Add the remote folder to the remote folder lookup table (used to update file revisions)
recordRemoteFolder(remoteRoot);
session = new Session(repository, remoteRoot, false);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
fetchFileRevisions(session, changedFiles.toArray(new String[changedFiles.size()]), Policy.subMonitorFor(monitor, 20));
} finally {
session.close();
}
}
return remoteFile;
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
}
private Command.KSubstOption getKeywordMode(ICVSFile file) throws CVSException {
if (file == null) return null;
byte[] syncBytes = file.getSyncBytes();
if (syncBytes == null) return null;
return ResourceSyncInfo.getKeywordMode(syncBytes);
}
/*
* 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
*/
RemoteFolder buildBaseTree(RemoteFolder parent, ICVSFolder local, IProgressMonitor monitor) throws CVSException {
Policy.checkCanceled(monitor);
// Create a remote folder tree corresponding to the local resource
FolderSyncInfo folderSyncInfo = local.getFolderSyncInfo();
if (folderSyncInfo == null) return null;
RemoteFolder remote = createRemoteFolder(local, parent, folderSyncInfo);
// Create a List to contain the created children
List<RemoteResource> 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);
RemoteFolder tree = buildBaseTree(remote, folder, monitor);
if (tree != null)
children.add(tree);
}
}
// 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];
byte[] syncBytes = file.getSyncBytes();
// if there is no sync info then there is no base
if (syncBytes==null)
continue;
// There is no remote if the file was added
if (ResourceSyncInfo.isAddition(syncBytes))
continue;
// If the file was deleted locally, we need to generate a new sync info without the delete flag
if (ResourceSyncInfo.isDeletion(syncBytes)) {
syncBytes = ResourceSyncInfo.convertFromDeletion(syncBytes);
}
children.add(createRemoteFile(remote, syncBytes));
monitor.worked(1);
}
// Remove any folders that are phantoms locally if they have no children
if (children.isEmpty() && isPruneEmptyDirectories() && !local.exists())
return null;
// Add the children to the remote folder tree
remote.setChildren(children.toArray(new ICVSRemoteResource[children.size()]));
return remote;
}
protected RemoteFile createRemoteFile(RemoteFolder remote, byte[] syncBytes) throws CVSException {
return new RemoteFile(remote, syncBytes);
}
protected RemoteFolder createRemoteFolder(ICVSFolder local, RemoteFolder parent, FolderSyncInfo folderSyncInfo) {
return new RemoteFolderTree(parent, local.getName(), repository, folderSyncInfo.getRepository(), folderSyncInfo.getTag());
}
/*
* 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, String localPath, IProgressMonitor monitor) throws CVSException {
Policy.checkCanceled(monitor);
// Add the remote folder to the remote folder lookup table (used to update file revisions)
recordRemoteFolder(remote);
// Create a map to contain the created children
Map<String, RemoteResource> 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 = fileDeltas.get(localPath);
if (deltas == null)
deltas = EMPTY_MAP;
// If there is a local, use the local children to start building 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(folder) && (d==null || d.getRevision() != DELETED)) {
children.put(folders[i].getName(),
new RemoteFolderTree(remote, folders[i].getName(), repository,
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());
byte[] syncBytes = file.getSyncBytes();
// if there is no sync info then there isn't a remote file for this local file on the
// server.
if (syncBytes==null)
continue;
// There is no remote if the file was added and we didn't get a conflict (C) indicator from the server
if (ResourceSyncInfo.isAddition(syncBytes) && 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 (ResourceSyncInfo.isDeletion(syncBytes) && d==null)
continue;
int type = d==null ? Update.STATE_NONE : d.getSyncState();
children.put(file.getName(), new RemoteFile(remote, type, syncBytes));
}
}
// 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) {
children.put(name, new RemoteFolderTree(remote, repository,
Util.appendPath(remote.getRepositoryRelativePath(), name),
tagForRemoteFolder(remote, tag)));
} else if (revision == ADDED) {
children.put(name, new RemoteFile(remote,
d.getSyncState(),
name,
null, /* the revision will be fetched later */
null, /* there's no way to know the remote keyword mode */
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,
null, /* the revision will be fetched later */
getKeywordMode((ICVSFile)children.get(name)), /* get the keyword mode from the local file*/
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(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<RemoteFolderTree> 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, Util.appendPath(localPath, name), monitor);
// Record any children that are empty
if (isPruneEmptyDirectories() && remoteFolder.getChildren().length == 0) {
// Prune if the local folder is also empty.
if (localFolder == null || (localFolder.members(ICVSFolder.ALL_EXISTING_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 (isPruneEmptyDirectories() && !emptyChildren.isEmpty()) {
List<ICVSRemoteResource> newChildren = new ArrayList<ICVSRemoteResource>();
newChildren.addAll(Arrays.asList(remote.getChildren()));
newChildren.removeAll(emptyChildren);
remote.setChildren(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<String> fetchDelta(Session session, String[] arguments, final IProgressMonitor monitor) throws CVSException {
// Create an listener that will accumulate new and removed files and folders
IUpdateMessageListener listener = new IUpdateMessageListener() {
public void directoryInformation(ICVSFolder root, String 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(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(path, 3) }));
}
}
public void directoryDoesNotExist(ICVSFolder root, String path) {
// Record removed directory with parent so it can be removed when building the parent
if (path.length() == 0) {
rootDoesNotExist = true;
} else {
recordDelta(path, DELETED, Update.STATE_NONE);
monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(path, 3) }));
}
}
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
Map deltas = fileDeltas.get(Util.removeLastSegment(filename));
DeltaNode d = deltas != null ? (DeltaNode)deltas.get(Util.getLastSegment(filename)) : 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(filename, UNKNOWN, type);
monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(filename, 3) }));
break;
}
}
public void fileDoesNotExist(ICVSFolder root, String filename) {
recordDelta(filename, DELETED, Update.STATE_NONE);
monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(filename, 3) }));
}
};
// 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.SYNCUPDATE.execute(session,
new GlobalOption[] { Command.DO_NOT_CHANGE },
updateLocalOptions,
arguments,
new UpdateListener(listener),
monitor);
if (status.getCode() == CVSStatus.SERVER_ERROR) {
CVSServerException e = new CVSServerException(status);
if (e.isNoTagException()) {
// This error indicates that the complete subtree
// being fetched does not have any files for the tag being queried
rootDoesNotExist = true;
} else if (e.containsErrors()) {
// Log the error
CVSProviderPlugin.log(e);
}
}
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, String 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, String 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(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(path, 3) }));
}
}
public void directoryDoesNotExist(ICVSFolder root, String path) {
}
public void fileInformation(int type, ICVSFolder root, String filename) {
// NOTE: Check path prefix
changedFiles.add(filename);
recordDelta(filename, ADDED, type);
monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String[] { Util.toTruncatedPath(filename, 3) }));
}
public void fileDoesNotExist(ICVSFolder root, String filename) {
}
};
// NOTE: Should use the path relative to the remoteRoot
IStatus status = Command.UPDATE.execute(session,
new GlobalOption[] { Command.DO_NOT_CHANGE },
updateLocalOptions,
new String[] { localPath },
new UpdateListener(listener),
Policy.subMonitorFor(monitor, 1));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
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 },
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 List<CVSException> exceptions = new ArrayList<CVSException>();
IStatusListener listener = new IStatusListener() {
public void fileStatus(ICVSFolder root, String path, String remoteRevision) {
try {
updateRevision(path, remoteRevision);
monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingRevision, new String[] { Util.toTruncatedPath(path, 3) }));
} 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 occurred fetching the revisions
if ( ! exceptions.isEmpty()) {
if (exceptions.size() == 1) {
throw exceptions.get(0);
} else {
MultiStatus multi = new MultiStatus(CVSProviderPlugin.ID, 0, CVSMessages.RemoteFolder_errorFetchingRevisions, null);
for (int i = 0; i < exceptions.size(); i++) {
multi.merge(exceptions.get(i).getStatus());
}
throw new CVSException(multi);
}
}
}
protected boolean isPruneEmptyDirectories() {
return false;
}
/*
* 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 revision of UNKNOWN indicates that the revision has not been fetched
* from the repository yet.
*/
private void recordDelta(String path, String revision, int syncState) {
if (revision == FOLDER) {
newFolderExist = true;
}
String parent = Util.removeLastSegment(path);
Map<String, DeltaNode> deltas = fileDeltas.get(parent);
if (deltas == null) {
deltas = new HashMap<>();
fileDeltas.put(parent, deltas);
}
String name = Util.getLastSegment(path);
deltas.put(name, new DeltaNode(name, revision, syncState));
}
private void updateRevision(String path, String revision) throws CVSException {
RemoteFolderTree folder = getRecoredRemoteFolder(Util.removeLastSegment(path));
if (folder == null) {
IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuilder_missingParent, new String[] { path.toString(), revision }), root);
throw new CVSException(status);
}
((RemoteFile)folder.getFile(Util.getLastSegment(path))).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 parent 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(ICVSFolder mFolder) throws CVSException {
return mFolder.isCVSFolder() && ! mFolder.isManaged() && ! mFolder.equals(root) && mFolder.getParent().isCVSFolder();
}
private void recordRemoteFolder(RemoteFolderTree remote) throws CVSException {
String path = remote.getFolderSyncInfo().getRemoteLocation();
remoteFolderTable.put(Util.asPath(path), remote);
}
private RemoteFolderTree getRecoredRemoteFolder(String path) {
return remoteFolderTable.get(Util.asPath(path));
}
/**
* This method returns an array of the files that differ between the local and remote trees.
* The files are represented as a String that contains the path to the file in the remote or local trees.
* @return an array of differing files
*/
public String[] getFileDiffs() {
return changedFiles.toArray(new String[changedFiles.size()]);
}
}