blob: 6399830a45804a9d102d024c50b1c483e972ce0a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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:
* Dan Rubel - initial API and implementation
* IBM Corporation - maintenance and modifications
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.team.core.ProjectSetCapability;
import org.eclipse.team.core.ProjectSetSerializationContext;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.client.Checkout;
import org.eclipse.team.internal.ccvs.core.client.Command;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.Request;
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.connection.CVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
import org.eclipse.team.internal.ccvs.core.filesystem.CVSURI;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.RemoteFolder;
import org.eclipse.team.internal.ccvs.core.resources.RemoteModule;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.KnownRepositories;
/**
* An object for serializing and deserializing of references to CVS based
* projects. Given a project, it can produce a UTF-8 encoded String which can be
* stored in a file. Given this String, it can load a project into the
* workspace. When necessary it can also prompt for addition repository
* information displaying Specify Repository Information dialog.
*
* @since 3.0
*/
public class CVSProjectSetCapability extends ProjectSetCapability {
/**
* Override superclass implementation to return an array of project references.
*
* @see ProjectSetSerializer#asReference(IProject[], ProjectSetSerializationContext, IProgressMonitor)
*/
public String[] asReference(
IProject[] projects,
ProjectSetSerializationContext context,
IProgressMonitor monitor)
throws TeamException {
String[] result = new String[projects.length];
for (int i = 0; i < projects.length; i++)
result[i] = asReference(projects[i]);
return result;
}
/**
* Answer a string representing the specified project
*
* @param project the project (not <code>null</code>)
* @return the project reference (not <code>null</code>)
* @throws CVSException
*/
private String asReference(IProject project) throws TeamException {
CVSTeamProvider provider = (CVSTeamProvider)RepositoryProvider.getProvider(project);
CVSWorkspaceRoot root = provider.getCVSWorkspaceRoot();
CVSRepositoryLocation location = CVSRepositoryLocation.fromString(root.getRemoteLocation().getLocation(false));
location.setUserMuteable(true);
ICVSFolder folder = root.getLocalRoot();
return asReference(location, folder, project);
}
private String asReference(CVSRepositoryLocation location, ICVSFolder folder, IProject project) throws TeamException {
StringBuffer buffer = new StringBuffer();
buffer.append("1.0,"); //$NON-NLS-1$
String repoLocation = location.getLocation();
buffer.append(repoLocation);
buffer.append(","); //$NON-NLS-1$
FolderSyncInfo syncInfo = folder.getFolderSyncInfo();
String module = syncInfo.getRepository();
buffer.append(module);
buffer.append(","); //$NON-NLS-1$
String projectName = project.getName();
buffer.append(projectName);
CVSTag tag = syncInfo.getTag();
if (tag != null) {
if (tag.getType() != CVSTag.DATE) {
buffer.append(","); //$NON-NLS-1$
String tagName = tag.getName();
buffer.append(tagName);
}
}
return buffer.toString();
}
/**
* Override superclass implementation to load the referenced projects into the workspace.
*
* @see org.eclipse.team.core.ProjectSetSerializer#addToWorkspace(java.lang.String[], org.eclipse.team.core.ProjectSetSerializationContext, org.eclipse.core.runtime.IProgressMonitor)
*/
public IProject[] addToWorkspace(
String[] referenceStrings,
ProjectSetSerializationContext context,
IProgressMonitor monitor)
throws TeamException {
monitor = Policy.monitorFor(monitor);
Policy.checkCanceled(monitor);
// Confirm the projects to be loaded
Map infoMap = new HashMap(referenceStrings.length);
IProject[] projects = asProjects(referenceStrings, infoMap);
projects = confirmOverwrite(context, projects);
if (projects == null || projects.length == 0 /* No projects to import */)
return new IProject[0];
Map suggestedRepositoryLocations = CVSRepositoryLocationMatcher
.prepareSuggestedRepositoryLocations(projects, infoMap);
applySinglePerfectMatchesToInfoMap(suggestedRepositoryLocations, infoMap);
if (CVSRepositoryLocationMatcher.isPromptRequired(suggestedRepositoryLocations)) {
// Display the dialog
Map userSelectedRepositoryLocations = promptForAdditionRepositoryInformation(suggestedRepositoryLocations);
// Replace repository location from a project load info with one from the prompter
if (userSelectedRepositoryLocations != null) {
applyUserSelectionToInfoMap(userSelectedRepositoryLocations, infoMap);
} else {
// operation canceled
return new IProject[0];
}
}
// Load the projects
return checkout(projects, infoMap, monitor);
}
private void applySinglePerfectMatchesToInfoMap(
Map/* <IProject, List<ICVSRepositoryLocation>> */suggestedRepositoryLocations,
Map/* <IProject, LoadInfo> */infoMap) throws CVSException {
for (Iterator i = infoMap.values().iterator(); i.hasNext();) {
LoadInfo loadInfoForProject = (LoadInfo) i.next();
ICVSRepositoryLocation key = loadInfoForProject.repositoryLocation;
List suggestedList = (List) suggestedRepositoryLocations.get(key);
/*
* We've found a (single) perfect match, the dialog should not
* prompt for additional info in this case. To use the matched repo
* we will need to recreate repository location with 'useKnown' flag
* set to true.
*/
if (suggestedList != null
&& suggestedList.size() == 1
&& CVSRepositoryLocationMatcher.isMatching(
(ICVSRepositoryLocation) suggestedList.get(0),
loadInfoForProject.repositoryLocation)) {
loadInfoForProject.repositoryLocation = getRepositoryLocationFromString(
loadInfoForProject.repositoryLocation.getLocation(true),
true, false);
// Do not display in the dialog.
suggestedRepositoryLocations.remove(key);
}
}
}
private void applyUserSelectionToInfoMap(
Map/* <ICVSRepositoryLocation, CVSRepositoryLocation> */userSelectedRepositoryLocations,
Map/* <IProject, LoadInfo> */infoMap)
throws CVSException {
for (Iterator i = infoMap.values().iterator(); i.hasNext();) {
LoadInfo loadInfoForProject = (LoadInfo) i.next();
ICVSRepositoryLocation userSelectedRepository = (ICVSRepositoryLocation) userSelectedRepositoryLocations
.get(loadInfoForProject.repositoryLocation);
// TODO: final modifier removed for LoadInfo.repositoryLocation
// another solution is to create a copy (clone) of LoadInfo, and replace the whole object
if (userSelectedRepository != null)
loadInfoForProject.repositoryLocation = userSelectedRepository;
}
}
/**
* Translate the reference strings into projects to be loaded
* and build a mapping of project to project load information.
*
* @param referenceStrings project references
* @param infoMap a mapping of project to project load information
* @return the projects to be loaded
*/
public static IProject[] asProjects(String[] referenceStrings, Map infoMap) throws CVSException {
Collection result = new ArrayList();
for (int i = 0; i < referenceStrings.length; i++) {
StringTokenizer tokenizer = new StringTokenizer(referenceStrings[i], ","); //$NON-NLS-1$
String version = tokenizer.nextToken();
// If this is a newer version, then ignore it
if (!version.equals("1.0")) //$NON-NLS-1$
continue;
LoadInfo info = new LoadInfo(tokenizer, false, false);
IProject proj = info.getProject();
result.add(proj);
infoMap.put(proj, info);
}
return (IProject[]) result.toArray(new IProject[result.size()]);
}
/**
* Checkout projects from the CVS repository
*
* @param projects the projects to be loaded from the repository
* @param infoMap a mapping of project to project load information
* @param monitor the progress monitor (not <code>null</code>)
*/
private IProject[] checkout(
final IProject[] projects,
final Map infoMap,
IProgressMonitor monitor)
throws TeamException {
final List result = new ArrayList();
try {
ResourcesPlugin.getWorkspace().run((IWorkspaceRunnable) monitor1 -> {
monitor1.beginTask("", 1000 * projects.length); //$NON-NLS-1$
try {
for (int i = 0; i < projects.length; i++) {
if (monitor1.isCanceled())
break;
IProject project = projects[i];
LoadInfo info = (LoadInfo) infoMap.get(project);
if (info != null && info.checkout(SubMonitor.convert(monitor1, 1000)))
result.add(project);
}
}
finally {
monitor1.done();
}
}, getCheckoutRule(projects), IResource.NONE, monitor);
} catch (CoreException e) {
throw TeamException.asTeamException(e);
}
return (IProject[])result.toArray(new IProject[result.size()]);
}
/**
* Internal class for adding projects to the workspace
*/
static class LoadInfo {
// TODO: final modifier removed in order to replace a repository location before check out
ICVSRepositoryLocation repositoryLocation;
private final String module;
private final IProject project;
private final CVSTag tag;
/**
* Construct a new instance wrapping the specified project reference
*
* @param tokenizer the StringTokenizer from which all data are extracted
*/
LoadInfo(StringTokenizer tokenizer, boolean useKnown, boolean addIfNotFound) throws CVSException {
String repo = tokenizer.nextToken();
repositoryLocation = getRepositoryLocationFromString(repo, useKnown, addIfNotFound);
module = tokenizer.nextToken();
String projectName = tokenizer.nextToken();
project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (tokenizer.hasMoreTokens()) {
String tagName = tokenizer.nextToken();
tag = new CVSTag(tagName, CVSTag.BRANCH);
}
else {
tag = null;
}
}
LoadInfo(StringTokenizer tokenizer) throws CVSException {
this(tokenizer, true, true);
}
/**
* Answer the project referenced by this object.
* The project may or may not already exist.
*
* @return the project (not <code>null</code>)
*/
private IProject getProject() {
return project;
}
/**
* Checkout the project specified by this reference.
*
* @param monitor project monitor
* @return true if loaded, else false
* @throws TeamException
*/
boolean checkout(IProgressMonitor monitor) throws TeamException {
if (repositoryLocation == null)
return false;
CVSProjectSetCapability.checkout(
repositoryLocation,
project,
module,
tag,
monitor);
return true;
}
}
/**
* Extract the CVS repository location information from the specified string
*
* @param repo the repository location as a string
* @param use a known repository which matches the one from the repo string
* @param addIfNotFound add newLocation to the list of known repositories
* @return the CVS repository information
* @throws CVSException
*/
private static ICVSRepositoryLocation getRepositoryLocationFromString(String repo, boolean useKnown, boolean addIfNotFound) throws CVSException {
// create the new location
ICVSRepositoryLocation newLocation = CVSRepositoryLocation.fromString(repo);
if (useKnown && (newLocation.getUsername() == null || newLocation.getUsername().length() == 0)) {
// look for an existing location that matches
ICVSRepositoryLocation[] locations = CVSProviderPlugin.getPlugin().getKnownRepositories();
for (int i = 0; i < locations.length; i++) {
ICVSRepositoryLocation location = locations[i];
if (CVSRepositoryLocationMatcher.isMatching(newLocation, location))
return location;
}
}
if (addIfNotFound)
// No existing location was found so add this location to the list
// of known repositories
KnownRepositories.getInstance().addRepository(newLocation, true);
return newLocation;
}
/**
* Checkout a CVS module.
*
* The provided project represents the target project. Any existing contents
* may or may not get overwritten. If project is <code>null</code> then a project
* will be created based on the provided sourceModule. If soureModule is null,
* then the project name will be used as the module to
* check out. If both are absent, an exception is thrown.
*
* Resources existing in the local file system at the target project location but now
* known to the workbench will be overwritten.
*
* After the successful completion of this method, the project will exist
* and be open.
*/
public static void checkout(
ICVSRepositoryLocation repository,
IProject project,
String sourceModule,
CVSTag tag,
IProgressMonitor monitor)
throws TeamException {
if (sourceModule == null)
sourceModule = project.getName();
checkout(new ICVSRemoteFolder[] { new RemoteFolder(null, repository, sourceModule, tag)},
new IProject[] { project }, monitor);
}
/**
* Checkout the remote resources into the local workspace. Each resource will
* be checked out into the corresponding project. If the corresponding project is
* null or if projects is null, the name of the remote resource is used as the name of the project.
*
* Resources existing in the local file system at the target project location but now
* known to the workbench will be overwritten.
*/
public static void checkout(final ICVSRemoteFolder[] resources, final IProject[] projects, final IProgressMonitor monitor) throws TeamException {
final TeamException[] eHolder = new TeamException[1];
try {
IWorkspaceRunnable workspaceRunnable = pm -> {
try {
pm.beginTask(null, 1000 * resources.length);
// Get the location of the workspace root
ICVSFolder root = CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot());
for (int i=0;i<resources.length;i++) {
IProject project = null;
RemoteFolder resource = (RemoteFolder)resources[i];
// Determine the provided target project if there is one
if (projects != null)
project = projects[i];
// Determine the remote module to be checked out
String moduleName;
if (resource instanceof RemoteModule) {
moduleName = ((RemoteModule)resource).getName();
} else {
moduleName = resource.getRepositoryRelativePath();
}
// Open a connection session to the repository
ICVSRepositoryLocation repository = resource.getRepository();
Session session = new Session(repository, root);
try {
session.open(Policy.subMonitorFor(pm, 50), false /* read-only */);
// Determine the local target projects (either the project provider or the module expansions)
final Set targetProjects = new HashSet();
if (project == null) {
// Fetch the module expansions
IStatus status1 = Request.EXPAND_MODULES.execute(session, new String[] {moduleName}, Policy.subMonitorFor(pm, 50));
if (status1.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status1);
}
// Convert the module expansions to local projects
String[] expansions = session.getModuleExpansions();
for (int j = 0; j < expansions.length; j++) {
targetProjects.add(ResourcesPlugin.getWorkspace().getRoot().getProject(new Path(null, expansions[j]).segment(0)));
}
} else {
targetProjects.add(project);
}
// Prepare the target projects to receive resources
root.run(monitor1 -> scrubProjects(
(IProject[]) targetProjects.toArray(new IProject[targetProjects.size()]), monitor1),
Policy.subMonitorFor(pm, 100));
// Build the local options
List 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));
// Perform the checkout
IStatus status2 = Command.CHECKOUT.execute(session,
Command.NO_GLOBAL_OPTIONS,
(LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]),
new String[]{moduleName},
null,
Policy.subMonitorFor(pm, 800));
if (status2.getCode() == CVSStatus.SERVER_ERROR) {
// XXX Should we cleanup any partially checked out projects?
throw new CVSServerException(status2);
}
// Bring the project into the workspace
refreshProjects((IProject[])targetProjects.toArray(new IProject[targetProjects.size()]), Policy.subMonitorFor(pm, 100));
} finally {
session.close();
}
}
}
catch (TeamException e) {
// Pass it outside the workspace runnable
eHolder[0] = e;
} finally {
pm.done();
}
// CoreException and OperationCanceledException are propagated
};
ResourcesPlugin.getWorkspace().run(workspaceRunnable, getCheckoutRule(projects), 0, monitor);
} catch (CoreException e) {
throw CVSException.wrapException(e);
} finally {
monitor.done();
}
// Re-throw the TeamException, if one occurred
if (eHolder[0] != null) {
throw eHolder[0];
}
}
private static ISchedulingRule getCheckoutRule(final IProject[] projects) {
if (projects.length == 1) {
return ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(projects[0]);
} else {
Set rules = new HashSet();
for (int i = 0; i < projects.length; i++) {
ISchedulingRule modifyRule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(projects[i]);
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((ISchedulingRule[]) rules.toArray(new ISchedulingRule[rules.size()]));
}
}
/*
* Bring the provied projects into the workspace
*/
/* internal use only */ static void refreshProjects(IProject[] projects, IProgressMonitor monitor) throws CoreException, TeamException {
monitor.beginTask(CVSMessages.CVSProvider_Creating_projects_2, projects.length * 100);
try {
for (int i = 0; i < projects.length; i++) {
IProject project = projects[i];
// Register the project with Team
RepositoryProvider.map(project, CVSProviderPlugin.getTypeId());
CVSTeamProvider provider = (CVSTeamProvider)RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId());
provider.setWatchEditEnabled(CVSProviderPlugin.getPlugin().isWatchEditEnabled());
}
} finally {
monitor.done();
}
}
/*
* Delete the target projects before checking out
*/
/* internal use only */ static void scrubProjects(IProject[] projects, IProgressMonitor monitor) throws CVSException {
if (projects == null) {
monitor.done();
return;
}
monitor.beginTask(CVSMessages.CVSProvider_Scrubbing_projects_1, projects.length * 100);
try {
for (int i=0;i<projects.length;i++) {
IProject project = projects[i];
if (project != null && project.exists()) {
if(!project.isOpen()) {
project.open(Policy.subMonitorFor(monitor, 10));
}
// 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
monitor.subTask(CVSMessages.CVSProvider_Scrubbing_local_project_1);
// unmap the project from any previous repository provider
if (RepositoryProvider.getProvider(project) != null)
RepositoryProvider.unmap(project);
IResource[] children = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 80);
subMonitor.beginTask(null, children.length * 100);
try {
for (int j = 0; j < children.length; j++) {
if ( ! children[j].getName().equals(".project")) {//$NON-NLS-1$
children[j].delete(true /*force*/, Policy.subMonitorFor(subMonitor, 100));
}
}
} finally {
subMonitor.done();
}
} else if (project != null) {
// Make sure there is no directory in the local file system.
File location = new File(project.getParent().getLocation().toFile(), project.getName());
if (location.exists()) {
deepDelete(location);
}
}
}
} catch (CoreException e) {
throw CVSException.wrapException(e);
} finally {
monitor.done();
}
}
private static void deepDelete(File resource) throws CVSException {
if (resource.isDirectory()) {
File[] fileList = resource.listFiles();
if(fileList == null) {
throw new CVSException("Content from directory '" + resource.getAbsolutePath() + "' can not be listed."); //$NON-NLS-1$ //$NON-NLS-2$
}
for (int i = 0; i < fileList.length; i++) {
deepDelete(fileList[i]);
}
}
resource.delete();
}
public String getProject(String referenceString) {
//team provider, cvs folder, project name
StringTokenizer tokenizer = new StringTokenizer(referenceString, ","); //$NON-NLS-1$
String version = tokenizer.nextToken();
// If this is a newer version, then ignore it
if (!version.equals("1.0")) //$NON-NLS-1$
return null;
try {
LoadInfo info = new LoadInfo(tokenizer);
return info.getProject().getName();
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return null;
}
}
public URI getURI(String referenceString) {
//team provider, cvs folder, project name
StringTokenizer tokenizer = new StringTokenizer(referenceString, ","); //$NON-NLS-1$
String version = tokenizer.nextToken();
// If this is a newer version, then ignore it
if (!version.equals("1.0")) //$NON-NLS-1$
return null;
try {
LoadInfo info = new LoadInfo(tokenizer);
CVSURI cvsURI = new CVSURI(info.repositoryLocation,new Path(info.module),info.tag);
return cvsURI.toURI();
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return null;
}
}
@Override
public String asReference(URI uri, String projectName) {
try {
CVSURI cvsURI = CVSURI.fromUri(uri);
ICVSRepositoryLocation location = cvsURI.getRepository();
ICVSFolder folder = cvsURI.toFolder();
if (projectName == null) {
projectName = cvsURI.getProjectName();
if (projectName == null)
projectName = cvsURI.getLastSegment();
if (projectName == null)
return null;
}
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
return asReference((CVSRepositoryLocation)location, folder, project);
} catch (TeamException e) {
CVSProviderPlugin.log(e);
return null;
}
}
private Map/* <ICVSRepositoryLocation, CVSRepositoryLocation> */promptForAdditionRepositoryInformation(
Map alternativeMap) {
IUserAuthenticator authenticator = CVSRepositoryLocation.getAuthenticator();
return authenticator.promptToConfigureRepositoryLocations(alternativeMap);
}
}