in source/com.microsoft.tfs.client.eclipse/src/com/microsoft/tfs/client/eclipse/TFSMoveDeleteHook.java [1225:1560]
private final ResourceInspectionResult inspectResource(
final IResourceTree tree,
final IResource resource,
final boolean resourceMustExist,
final boolean deferIgnoredResources,
final ChangeType deniedChangeTypes,
final int updateFlags,
final boolean deletingFolder,
final IProgressMonitor progressMonitor) {
progressMonitor.beginTask(MessageFormat.format(
Messages.getString("TFSMoveDeleteHook.ExaminingResourceFormat"), //$NON-NLS-1$
resource.getFullPath()), 10);
try {
/* File is a linked resource, exit quickly */
if (resource.getLocation() == null || resource.isLinked(IResource.CHECK_ANCESTORS)) {
log.info(MessageFormat.format("Cannot move/delete linked resource {0}", resource)); //$NON-NLS-1$
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
}
/* Resource has already been removed from the workspace */
else if (resourceMustExist && !resource.exists()) {
log.warn(
MessageFormat.format("Resource {0} no longer exists in the workspace.", resource.getFullPath())); //$NON-NLS-1$
}
final boolean isIgnored = IGNORED_RESOURCES_FILTER.filter(resource).isReject();
if (deferIgnoredResources && isIgnored) {
log.debug(MessageFormat.format("Ignoring resource {0}", resource)); //$NON-NLS-1$
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
}
final String resourcePath = resource.getLocation().toOSString();
if (resourceMustExist) {
final File resourceFile = new File(resourcePath);
/* Ensure that the file exists on disk */
if (!resourceFile.exists()) {
log.warn(MessageFormat.format("Resource {0} no longer exists on disk", resourcePath)); //$NON-NLS-1$
}
/*
* Ensure that the type in the tree is the same type as on the
* file system
*/
if (resource instanceof IFile && resourceFile.isDirectory()) {
throw new RuntimeException(MessageFormat.format(
//@formatter:off
Messages.getString("TFSMoveDeleteHook.ResourceExistsAsFileInWorkspaceButFolderOnDiskFormat"), //$NON-NLS-1$
//@formatter:on
resource.getFullPath()));
} else if (!(resource instanceof IFile) && resourceFile.isFile()) {
throw new RuntimeException(MessageFormat.format(
//@formatter:off
Messages.getString("TFSMoveDeleteHook.ResourceExistsAsFolderInWorkspaceButFileOnDiskFormat"), //$NON-NLS-1$
//@formatter:on
resource.getFullPath()));
}
}
/* See if the tree is in sync */
final int depth = (resource instanceof IFile) ? IResource.DEPTH_ZERO : IResource.DEPTH_INFINITE;
if ((updateFlags & IResource.FORCE) != IResource.FORCE && !tree.isSynchronized(resource, depth)) {
throw new RuntimeException(
MessageFormat.format(
Messages.getString("TFSMoveDeleteHook.ResourceNotInSyncWithLocalFileSystemFormat"), //$NON-NLS-1$
resource.getFullPath()));
}
/* See if we're online */
final ResourceRepositoryMap repositoryMap = PluginResourceHelpers.mapResources(new IResource[] {
resource
});
final TFSRepository repository = repositoryMap.getRepository(resource);
/* Offline */
if (repository == null) {
log.info(
MessageFormat.format(
"Repository is offline, delete for {0} must be reconciled when returning online to Team Foundation Server.", //$NON-NLS-1$
resource.getFullPath()));
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
} else if (!repository.equals(repositoryProvider.getRepository())) {
log.warn(
MessageFormat.format(
"Repository for resource {0} mapped to {1} but repository provider returned {2}", //$NON-NLS-1$
resource.getFullPath(),
repository,
repositoryProvider.getRepository()));
}
/*
* Test if this item has an invalid TFS name/path (which would cause
* errors in the queries below).
*/
try {
repository.getWorkspace().getMappedServerPath(resourcePath);
} catch (final InputValidationException e) {
log.warn(MessageFormat.format("Ignoring resource {0} because it is not a valid TFS path", resource), e); //$NON-NLS-1$
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
}
/*
* See if the resource exists in the ResourceDataManager (ie, if it
* exists in TFS). If the project exists in TFS but the file is not,
* then ResourceDataManager.hasCompletedRefresh will be true but
* ResourceDataManager.getResourceData will be null.
*/
final boolean needsQuery;
if (resourceMustExist) {
final ResourceDataManager resourceDataManager =
TFSEclipseClientPlugin.getDefault().getResourceDataManager();
if (!resourceDataManager.hasCompletedRefresh(resource.getProject())
|| resourceDataManager.getResourceData(resource) == null) {
needsQuery = true;
} else {
needsQuery = false;
}
} else {
needsQuery = false;
}
/*
* This is a particularly complicated and ugly work-around for
* server workspaces and Eclipse's copy and paste behavior.
*
* JDT (and probably others) implements "paste" that overwrites an
* existing file by:
*
* 1. Pending an edit on the target
*
* 2. Deleting the target
*
* 3. Copying the source to the target
*
* In a server workspace, the user may have the
* "check out files in background" option enabled, which uses
* another thread to do the checkout. The problem is that we may
* arrive at this point in this method before the background thread
* (started at step #1) has completed the checkout. Querying the
* pending changes might miss the "edit" change that we need to know
* about to do the MoveDeleteHook work.
*
* So we we must wait if the file is being checked out in the
* background.
*/
if (repository.getWorkspace().getLocation() == WorkspaceLocation.SERVER && resource instanceof IFile) {
final TFSFileModificationValidator validator = repositoryProvider.getTFSFileModificationValidator();
try {
new ExtensionPointAsyncObjectWaiter().waitUntilTrue(new IAsyncObjectWaiter.Predicate() {
@Override
public boolean isTrue() {
return !validator.isCheckingOutFileInBackground((IFile) resource);
}
});
} catch (final InterruptedException e) {
throw new VersionControlException(
MessageFormat.format(
Messages.getString("TFSMoveDeleteHook.InterruptedWaitingForBackgroundCheckoutFormat"), //$NON-NLS-1$
resource.getName()));
}
}
final List<PendingChange> pendingChanges = getNestedPendingChanges(repository, resourcePath);
log.info("Nested pending changes count: [" + String.valueOf(pendingChanges.size()) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
if (deletingFolder) {
/*
* Test if there is a pending rename with the source path
* including the item's path. That happens when Eclipse cleans
* up empty folders left after a package renaming. We don't need
* to do anything and skip the operation in the Eclipse
* platform.
*/
final String serverPath = repository.getWorkspace().getMappedServerPath(resourcePath);
log.info("Local folder to be deleted: " + resourcePath); //$NON-NLS-1$
log.info("Server folder to be deleted: " + serverPath); //$NON-NLS-1$
boolean reject = false;
for (final PendingChange change : pendingChanges) {
if (change.getChangeType().contains(ChangeType.RENAME)
&& !isSamePendingChange(serverPath, resourcePath, change)) {
log.info("Child pending change:"); //$NON-NLS-1$
log.info(" ChangeType: " + change.getChangeType().toString()); //$NON-NLS-1$
log.info(" ServerItem: " + change.getServerItem()); //$NON-NLS-1$
log.info(" LocalItem: " + (change.getLocalItem() == null ? "null" : change.getLocalItem())); //$NON-NLS-1$ //$NON-NLS-2$
log.info(" SourceLocalItem: " //$NON-NLS-1$
+ (change.getSourceLocalItem() == null ? "null" : change.getSourceLocalItem())); //$NON-NLS-1$
log.info(" SourceServerItem: " //$NON-NLS-1$
+ (change.getSourceServerItem() == null ? "null" : change.getSourceServerItem())); //$NON-NLS-1$
log.info(" Date: " //$NON-NLS-1$
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS z").format( //$NON-NLS-1$
change.getWebServiceObject().getDate().getTime()));
reject = true;
}
}
if (reject) {
progressMonitor.worked(1);
return new ResourceInspectionResult(
ResourceInspectionStatus.WARN,
repository,
true,
pendingChanges.toArray(new PendingChange[pendingChanges.size()]),
isIgnored,
Messages.getString("TFSMoveDeleteHook.CannotDeleteFolder")); //$NON-NLS-1$
}
}
/*
* If there are pending changes, see if they are of a disallowed
* type.
*/
boolean reject = false;
for (final PendingChange change : pendingChanges) {
if (change.getChangeType().containsAny(deniedChangeTypes)) {
log.info(" ChangeType: " + change.getChangeType().toString()); //$NON-NLS-1$
log.info(" ServerItem: " + change.getServerItem()); //$NON-NLS-1$
log.info(" LocalItem: " + (change.getLocalItem() == null ? "null" : change.getLocalItem())); //$NON-NLS-1$ //$NON-NLS-2$
log.info(" SourceLocalItem: " //$NON-NLS-1$
+ (change.getSourceLocalItem() == null ? "null" : change.getSourceLocalItem())); //$NON-NLS-1$
log.info(" SourceServerItem: " //$NON-NLS-1$
+ (change.getSourceServerItem() == null ? "null" : change.getSourceServerItem())); //$NON-NLS-1$
log.info(" Date: " //$NON-NLS-1$
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS z").format( //$NON-NLS-1$
change.getWebServiceObject().getDate().getTime()));
reject = true;
}
}
if (reject) {
log.warn(MessageFormat.format(
//@formatter:off
Messages.getString("TFSMoveDeleteHook.ResourceHasPendingChangePleaseUndoBeforeContinuingFormat"), //$NON-NLS-1$
//@formatter:on
resource.getLocation().toOSString()));
progressMonitor.worked(1);
throw new RuntimeException(MessageFormat.format(
//@formatter:off
Messages.getString("TFSMoveDeleteHook.ResourceHasPendingChangePleaseUndoBeforeContinuingFormat"), //$NON-NLS-1$
//@formatter:on
resource.getLocation().toOSString()));
}
/*
* If there are no pending changes and we didn't have any
* ResourceData for this resource, we need to query the server to
* make sure the item exists.
*/
boolean inServer = true;
if (needsQuery && WorkspaceLocation.LOCAL.equals(repository.getWorkspace().getLocation())) {
/*
* Local workspaces should query extended items to avoid hitting
* the server.
*/
final QueryItemsExtendedCommand queryCommand =
new QueryItemsExtendedCommand(repository, new ItemSpec[] {
new ItemSpec(resourcePath, RecursionType.NONE)
}, DeletedState.NON_DELETED, ItemType.ANY, GetItemsOptions.LOCAL_ONLY);
final IStatus queryStatus = runCommand(queryCommand, new SubProgressMonitor(progressMonitor, 9));
if (!queryStatus.isOK()) {
throw new RuntimeException(queryStatus.getMessage());
}
final ExtendedItem[][] items = queryCommand.getItems();
if (items == null || items.length == 0 || items[0] == null || items[0].length == 0) {
/*
* The item does not exist on the server (and is not an
* implicit add.)
*/
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
}
} else if (needsQuery) {
final QueryItemsCommand queryCommand = new QueryItemsCommand(
repository,
new ItemSpec[] {
new ItemSpec(resourcePath, RecursionType.NONE)
},
new WorkspaceVersionSpec(repository.getWorkspace()),
DeletedState.NON_DELETED,
ItemType.ANY,
GetItemsOptions.NONE);
final IStatus queryStatus = runCommand(queryCommand, new SubProgressMonitor(progressMonitor, 9));
if (!queryStatus.isOK()) {
throw new RuntimeException(queryStatus.getMessage());
}
final ItemSet[] itemSets = queryCommand.getItemSets();
/* Item does not exist in TFS */
if (itemSets == null
|| itemSets.length == 0
|| itemSets[0].getItems() == null
|| itemSets[0].getItems().length == 0) {
/*
* Special case: pending adds of files may implicitly add a
* folder. If this file does not exist on the server and is
* not an implicit add, ignore it.
*/
if (pendingChanges.size() == 0) {
return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
}
/*
* An implicit add that does not exist on the server.
*/
inServer = false;
}
}
/* Allow the deleteFile/deleteFolder methods to continue. */
progressMonitor.worked(1);
return new ResourceInspectionResult(
repository,
inServer,
pendingChanges.toArray(new PendingChange[pendingChanges.size()]),
isIgnored);
} finally {
progressMonitor.done();
}
}