blob: 9d004bca28f736d1de15e0c453f11959a09994b0 [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
* Philippe Ombredanne - bug 84808
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.operations;
import java.io.File;
import java.io.IOException;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.wizards.WorkingSetsDialog;
import org.eclipse.ui.*;
/**
* This class acts as an abstract class for checkout operations.
* It provides a few common methods.
*/
public abstract class CheckoutProjectOperation extends CheckoutOperation {
private String targetLocation;
public CheckoutProjectOperation(IWorkbenchPart part, ICVSRemoteFolder[] remoteFolders, String targetLocation) {
super(part, remoteFolders);
this.targetLocation = targetLocation;
}
/**
* Create and open the project, using a custom location if there is one.
*
* @param project
* @param monitor
* @throws CVSException
*/
protected void createAndOpenProject(IProject project, IProgressMonitor monitor) throws CVSException {
try {
monitor.beginTask(null, 5);
IProjectDescription desc = getDescriptionFor(project);
if (project.exists()) {
if (desc != null) {
project.move(desc, true, Policy.subMonitorFor(monitor, 3));
}
} else {
if (desc == null) {
// create in default location
project.create(Policy.subMonitorFor(monitor, 3));
} else {
// create in some other location
project.create(desc, Policy.subMonitorFor(monitor, 3));
}
}
if (!project.isOpen()) {
project.open(Policy.subMonitorFor(monitor, 2));
}
} catch (CoreException e) {
throw CVSException.wrapException(e);
} finally {
monitor.done();
}
}
protected IProjectDescription getDescriptionFor(IProject project) {
if (targetLocation == null) return null;
String projectName = project.getName();
IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(projectName);
description.setLocation(getTargetLocationFor(project));
return description;
}
/**
* Return the target location where the given project should be located or
* null if the default location should be used.
*
* @param project
*/
protected IPath getTargetLocationFor(IProject project) {
if (targetLocation == null) return null;
return new Path(targetLocation);
}
protected String getRemoteModuleName(ICVSRemoteFolder resource) {
String moduleName;
if (resource.isDefinedModule()) {
moduleName = resource.getName();
} else {
moduleName = resource.getRepositoryRelativePath();
}
return moduleName;
}
protected IStatus checkout(final ICVSRemoteFolder resource, IProject project, IProgressMonitor pm) throws CVSException {
// Get the location and the workspace root
ICVSFolder root = CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot());
ICVSRepositoryLocation repository = resource.getRepository();
// Open a connection session to the repository
final Session session = new Session(repository, root);
pm.beginTask(null, 100);
Policy.checkCanceled(pm);
session.open(Policy.subMonitorFor(pm, 5), false /* read-only */);
try {
// Check to see if the entire repo is being checked out.
if (project == null && resource.getName().equals(".")) { //$NON-NLS-1$
// No project was specified but we need on for this to work
String name = new Path(null, resource.getRepository().getRootDirectory()).lastSegment();
project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
}
// Check to see if using remote metafile project description name is preferred
if (project == null
&& CVSUIPlugin.getPlugin().isUseProjectNameOnCheckout()
&& resource instanceof RemoteProjectFolder) {
RemoteProjectFolder rpf = (RemoteProjectFolder) resource;
if (rpf.hasProjectName())
{
// no project was specified but we need to attempt the creation of one
// based on the metafile project name
project = ResourcesPlugin.getWorkspace().getRoot().getProject(rpf.getProjectName());
}
}
// Determine the local target projects (either the project provider or the module expansions)
// Note: Module expansions can be run over the same connection as a checkout
final IProject[] targetProjects = determineProjects(session, resource, project, Policy.subMonitorFor(pm, 5));
if (targetProjects == null) {
// An error occurred and was recorded so return it
return getLastError();
} else if (targetProjects.length == 0) {
return OK;
}
final boolean sendModuleName = project != null;
final IStatus[] result = new IStatus[] { null };
final ISchedulingRule schedulingRule = getSchedulingRule(targetProjects);
if (schedulingRule instanceof IResource && ((IResource)schedulingRule).getType() == IResource.ROOT) {
// One of the projects is mapped to a provider that locks the workspace.
// Just return the workspace root rule
try {
Job.getJobManager().beginRule(schedulingRule, pm);
// Still use the projects as the inner rule so we get the proper batching of sync info write
EclipseSynchronizer
.getInstance().run(
MultiRule.combine(targetProjects), monitor -> result[0] = performCheckout(session,
resource, targetProjects, sendModuleName, monitor),
Policy.subMonitorFor(pm, 90));
} finally {
Job.getJobManager().endRule(schedulingRule);
}
} else {
EclipseSynchronizer.getInstance().run(schedulingRule, monitor -> result[0] = performCheckout(session,
resource, targetProjects, sendModuleName, monitor), Policy.subMonitorFor(pm, 90));
}
IWorkingSet[] ws = getWorkingSets();
if (ws != null) {
for (IWorkingSet w : ws) {
createWorkingSet(w.getName(), targetProjects);
}
}
return result[0];
} catch (CVSException e) {
// An exception occurred either during the module-expansion or checkout
// Since we were able to make a connection, return the status so the
// checkout of any other modules can proceed
return new CVSStatus(e.getStatus().getSeverity(), NLS.bind(CVSUIMessages.CheckoutProjectOperation_1, new String[] {resource.getRepositoryRelativePath(), e.getMessage()}), e);
} finally {
session.close();
pm.done();
}
}
private ISchedulingRule getSchedulingRule(IProject[] projects) {
if (projects.length == 1) {
return ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(projects[0]);
} else {
Set<ISchedulingRule> rules = new HashSet<>();
for (IProject project : projects) {
ISchedulingRule modifyRule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(project);
if (modifyRule instanceof IResource && ((IResource)modifyRule).getType() == IResource.ROOT) {
// One of the projects is mapped to a provider that locks the workspace.
// Just return the workspace root rule
return modifyRule;
}
rules.add(modifyRule);
}
return new MultiRule(rules.toArray(new ISchedulingRule[rules.size()]));
}
}
/* private */ IStatus performCheckout(Session session, ICVSRemoteFolder resource, IProject[] targetProjects, boolean sendModuleName, IProgressMonitor pm) throws CVSException {
// Set the task name of the progress monitor to let the user know
// which project we're on. Don't use subTask since that will be
// changed when the checkout command is run.
String taskName;
if (targetProjects.length == 1) {
taskName = NLS.bind(CVSUIMessages.CheckoutProjectOperation_8, new String[] { resource.getName(), targetProjects[0].getName() });
} else {
taskName = NLS.bind(CVSUIMessages.CheckoutProjectOperation_9, new String[] { resource.getName(), String.valueOf(targetProjects.length) });
}
pm.beginTask(taskName, 100);
pm.setTaskName(taskName);
Policy.checkCanceled(pm);
try {
// Scrub the local contents if requested
if (performScrubProjects()) {
IStatus result = scrubProjects(resource, targetProjects, Policy.subMonitorFor(pm, 9));
if (!result.isOK()) {
return result;
}
}
// Determine if t
// in which case we'll use -d to flatten the directory structure.
// Only flatten the directory structure if the folder is not a root folder
IProject project = null;
if (targetProjects.length == 1) {
if (sendModuleName) {
project = targetProjects[0];
} else if (targetProjects[0].getName().equals(resource.getName())) {
// The target project has the same name as the remote folder.
// If the repository relative path has multiple segments
// we will want to flatten the directory structure
String path = resource.getRepositoryRelativePath();
if (!path.equals(FolderSyncInfo.VIRTUAL_DIRECTORY)
&& new Path(null, path).segmentCount() > 1) {
project = targetProjects[0];
}
}
}
try {
// Build the local options
List<LocalOption> localOptions = new ArrayList<>();
// Add the option to load into the target project if one was supplied
if (project != null) {
localOptions.add(Checkout.makeDirectoryNameOption(project.getName()));
}
// Prune empty directories if pruning enabled
if (CVSProviderPlugin.getPlugin().getPruneEmptyDirectories())
localOptions.add(Command.PRUNE_EMPTY_DIRECTORIES);
// Add the options related to the CVSTag
CVSTag tag = resource.getTag();
if (tag == null) {
// A null tag in a remote resource indicates HEAD
tag = CVSTag.DEFAULT;
}
localOptions.add(Update.makeTagOption(tag));
if (!isRecursive())
localOptions.add(Command.DO_NOT_RECURSE);
// Perform the checkout
IStatus status = Command.CHECKOUT.execute(session,
Command.NO_GLOBAL_OPTIONS,
localOptions.toArray(new LocalOption[localOptions.size()]),
new String[]{getRemoteModuleName(resource)},
null,
Policy.subMonitorFor(pm, 90));
return status;
} finally {
// Map the projects if they have CVS meta infomation even if a failure occurred
refreshProjects(targetProjects, Policy.subMonitorFor(pm, 1));
}
} finally {
pm.done();
}
}
protected boolean isRecursive() {
return true;
}
/*
* Determine the workspace project(s) that will be affected by the checkout.
* If project is not null, then it will be the only target project of the checkout.
* Otherwise, the remote folder could expand to multiple projects.
*
* If the remote resource is a folder which is not a root folder (i.e. a/b/c),
* then the target project will be the last segment (i.e. c).
*/
private IProject[] determineProjects(Session session, final ICVSRemoteFolder remoteFolder, IProject project, IProgressMonitor pm) throws CVSException {
Set<IProject> targetProjectSet = new HashSet<>();
String moduleName = getRemoteModuleName(remoteFolder);
if (project == null) {
// Fetch the module expansions
Policy.checkCanceled(pm);
IStatus status = Request.EXPAND_MODULES.execute(session, new String[] {moduleName}, pm);
if (status.getCode() == CVSStatus.SERVER_ERROR) {
collectStatus(status);
return null;
}
// Convert the module expansions to local projects
String[] expansions = session.getModuleExpansions();
if (expansions.length == 1 && expansions[0].equals(moduleName)) {
// For a remote folder, use the last segment as the project to be created
String lastSegment = new Path(null, expansions[0]).lastSegment();
// if using metafile project name is preferred, use it
if (CVSUIPlugin.getPlugin().isUseProjectNameOnCheckout() && remoteFolder instanceof RemoteProjectFolder) {
RemoteProjectFolder rpf = (RemoteProjectFolder) remoteFolder;
if (rpf.hasProjectName()) {
lastSegment = rpf.getProjectName();
}
}
targetProjectSet.add(ResourcesPlugin.getWorkspace().getRoot().getProject(lastSegment));
} else {
for (String expansion : expansions) {
targetProjectSet.add(ResourcesPlugin.getWorkspace().getRoot().getProject(new Path(null, expansion).segment(0)));
}
}
} else {
targetProjectSet.add(project);
}
// Return the local projects affected by the checkout
IProject[] targetProjects = targetProjectSet.toArray(new IProject[targetProjectSet.size()]);
return targetProjects;
}
/**
* Return true if the target projects should be scrubbed before the checkout occurs.
* Default is to scrub the projects. Can be overridden by subclasses.
*/
protected boolean performScrubProjects() {
return true;
}
/*
* This method is invoked to scrub the local projects that are the check out target of
* a single remote module.
*/
private IStatus scrubProjects(ICVSRemoteFolder remoteFolder, IProject[] projects, IProgressMonitor monitor) throws CVSException {
if (projects == null) {
monitor.done();
return OK;
}
// Prompt first before any work is done
if (projects.length > 1) {
setInvolvesMultipleResources(true);
}
for (IProject project : projects) {
Policy.checkCanceled(monitor);
if (needsPromptForOverwrite(project) && !promptToOverwrite(remoteFolder, project)) {
// User said no to this project but not no to all
return new CVSStatus(IStatus.INFO, IStatus.CANCEL, NLS.bind(CVSUIMessages.CheckoutProjectOperation_0, new String[] { remoteFolder.getRepositoryRelativePath() }), remoteFolder);
}
}
// Create the projects and remove any previous content
monitor.beginTask(null, projects.length * 100);
for (IProject project : projects) {
createAndOpenProject(project, Policy.subMonitorFor(monitor, 10));
scrubProject(project, Policy.subMonitorFor(monitor, 90));
}
monitor.done();
return OK;
}
private void scrubProject(IProject project, IProgressMonitor monitor) throws CVSException {
try {
// unmap the project from any previous repository provider
if (RepositoryProvider.getProvider(project) != null)
RepositoryProvider.unmap(project);
// We do not want to delete the project to avoid a project deletion delta
// We do not want to delete the .project to avoid core exceptions
IResource[] children = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
Policy.checkCanceled(monitor);
monitor.beginTask(null, 100 + children.length * 100);
monitor.subTask(NLS.bind(CVSUIMessages.CheckoutOperation_scrubbingProject, new String[] { project.getName() })); //
try {
for (IResource child : children) {
if (!child.getName().equals(".project")) { //$NON-NLS-1$
child.delete(true /*force*/, Policy.subMonitorFor(monitor, 100));
}
}
// Make sure there is no sync info cached for the project since
// a reader thread may have caused it to be loaded since the unmap.
EclipseSynchronizer.getInstance().flush(project, true, Policy.subMonitorFor(monitor, 100));
} finally {
monitor.done();
}
} catch (TeamException e) {
throw CVSException.wrapException(e);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
protected boolean needsPromptForOverwrite(IProject project) {
// First, check the description location
IProjectDescription desc = getDescriptionFor(project);
if (desc != null) {
File localLocation = desc.getLocation().toFile();
if (localLocation.exists()) return true;
}
// Next, check if the resource itself exists
if (project.exists()) return true;
// Finally, check if the location in the workspace exists;
File localLocation = getFileLocation(project);
if (localLocation.exists()) return true;
// The target doesn't exist
return false;
}
protected File getFileLocation(IProject project) {
return new File(project.getParent().getLocation().toFile(), project.getName());
}
private boolean promptToOverwrite(ICVSRemoteFolder remoteFolder, IProject project) {
// First, if the project exists in the workspace, prompt
if (project.exists()) {
if (!promptToOverwrite(
CVSUIMessages.CheckoutOperation_confirmOverwrite,
NLS.bind(CVSUIMessages.CheckoutOperation_thisResourceExists, new String[] { project.getName(), getRemoteModuleName(remoteFolder) }),
project)) {
return false;
}
}
// Even if the project exists, check the target location
IPath path = getTargetLocationFor(project);
File localLocation = null;
if (path == null) {
try {
// There is no custom location. However, still prompt
// if the project directory in the workspace directory
// would be overwritten.
if (!project.exists() || !project.isOpen() || project.getDescription().getLocation() != null) {
localLocation = getFileLocation(project);
}
} catch (CoreException e) {
CVSUIPlugin.log(e);
}
} else {
localLocation = path.toFile();
}
if (localLocation != null && localLocation.exists()) {
try {
return (promptToOverwrite(
CVSUIMessages.CheckoutOperation_confirmOverwrite,
NLS.bind(CVSUIMessages.CheckoutOperation_thisExternalFileExists, new String[] { localLocation.getCanonicalPath(), getRemoteModuleName(remoteFolder) }),
project));
} catch (IOException e) {
CVSUIPlugin.log(CVSException.wrapException(e));
}
}
return true;
}
/*
* Bring the provied projects into the workspace
*/
private void refreshProjects(IProject[] projects, IProgressMonitor monitor) throws CVSException {
monitor.beginTask(null, projects.length * 100);
try {
for (IProject project : projects) {
// Register the project with Team
try {
monitor.subTask(NLS.bind(CVSUIMessages.CheckoutOperation_refreshingProject, new String[] { project.getName() }));
ICVSFolder folder = CVSWorkspaceRoot.getCVSFolderFor(project);
if (folder.isCVSFolder()) {
RepositoryProvider.map(project, CVSProviderPlugin.getTypeId());
}
} catch (TeamException e) {
throw CVSException.wrapException(e);
}
CVSTeamProvider provider = (CVSTeamProvider)RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId());
if (provider != null) {
provider.setWatchEditEnabled(CVSProviderPlugin.getPlugin().isWatchEditEnabled());
}
}
} finally {
monitor.done();
}
}
@Override
protected String getTaskName() {
ICVSRemoteFolder[] remoteFolders = getRemoteFolders();
if (remoteFolders.length == 1) {
return NLS.bind(CVSUIMessages.CheckoutSingleProjectOperation_taskname, new String[] { remoteFolders[0].getName() });
} else {
return NLS.bind(CVSUIMessages.CheckoutMultipleProjectsOperation_taskName, new String[] { Integer.valueOf(remoteFolders.length).toString() });
}
}
/* private */ void createWorkingSet(String workingSetName, IProject[] projects) {
IWorkingSetManager manager = PlatformUI.getWorkbench().getWorkingSetManager();
IWorkingSet oldSet = manager.getWorkingSet(workingSetName);
if (oldSet == null) {
IWorkingSet newSet = manager.createWorkingSet(workingSetName, projects);
newSet.setId(WorkingSetsDialog.resourceWorkingSetId);
manager.addWorkingSet(newSet);
} else {
//don't overwrite the old elements
IAdaptable[] tempElements = oldSet.getElements();
IAdaptable[] adaptedProjects = oldSet.adaptElements(projects);
IAdaptable[] finalElementList = new IAdaptable[tempElements.length + adaptedProjects.length];
System.arraycopy(tempElements, 0, finalElementList, 0, tempElements.length);
System.arraycopy(adaptedProjects, 0,finalElementList, tempElements.length, adaptedProjects.length);
oldSet.setElements(finalElementList);
}
}
/*
* Returns the working sets to add the checked out projects to or null for none
*/
protected IWorkingSet[] getWorkingSets() {
return null;
}
}