/*******************************************************************************
* Copyright (C) 2011, 2012 Robin Stocker <robin@nibor.org>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.core.internal;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.ProjectReference;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.op.CloneOperation;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
/**
* Processes project references, clones and imports them.
*/
public class ProjectReferenceImporter {
private final String[] referenceStrings;
/**
* @param referenceStrings the reference strings to import
*/
public ProjectReferenceImporter(String[] referenceStrings) {
this.referenceStrings = referenceStrings;
}
/**
* Imports the projects as described in the reference strings.
*
* @param monitor progress monitor
* @return the imported projects
* @throws TeamException
*/
public List<IProject> run(IProgressMonitor monitor) throws TeamException {
final Map<URIish, Map<String, Set<ProjectReference>>> repositories = parseReferenceStrings();
final List<IProject> importedProjects = new ArrayList<IProject>();
for (final Map.Entry<URIish, Map<String, Set<ProjectReference>>> entry : repositories
.entrySet()) {
final URIish gitUrl = entry.getKey();
final Map<String, Set<ProjectReference>> refs = entry
.getValue();
for (final Map.Entry<String, Set<ProjectReference>> refEntry : refs
.entrySet()) {
final String refName = refEntry.getKey();
final Set<ProjectReference> projects = refEntry.getValue();
final Set<String> allRefs = refs.keySet();
File repositoryPath = null;
if (allRefs.size() == 1)
repositoryPath = findConfiguredRepository(gitUrl);
if (repositoryPath == null) {
try {
IPath workDir = getWorkingDir(gitUrl, refName, refs.keySet());
repositoryPath = cloneIfNecessary(gitUrl, refName, workDir, projects, monitor);
} catch (final InterruptedException e) {
// was canceled by user
return Collections.emptyList();
}
}
getRepositoryUtil().addConfiguredRepository(repositoryPath);
IPath newWorkDir = new Path(repositoryPath.getAbsolutePath())
.removeLastSegments(1);
List<IProject> p = importProjects(projects, newWorkDir,
repositoryPath, monitor);
importedProjects.addAll(p);
}
}
return importedProjects;
}
private static File cloneIfNecessary(final URIish gitUrl, final String refToCheckout, final IPath workDir,
final Set<ProjectReference> projects, IProgressMonitor monitor) throws TeamException, InterruptedException {
final File repositoryPath = workDir.append(Constants.DOT_GIT_EXT).toFile();
if (workDir.toFile().exists()) {
if (repositoryAlreadyExistsForUrl(repositoryPath, gitUrl))
return repositoryPath;
else {
final Collection<String> projectNames = new LinkedList<String>();
for (final ProjectReference projectReference : projects)
projectNames.add(projectReference.getProjectDir());
throw new TeamException(
NLS.bind(CoreText.GitProjectSetCapability_CloneToExistingDirectory,
new Object[] { workDir, projectNames, gitUrl }));
}
} else {
try {
int timeout = 60;
final CloneOperation cloneOperation = new CloneOperation(
gitUrl, true, null, workDir.toFile(), refToCheckout,
Constants.DEFAULT_REMOTE_NAME, timeout);
cloneOperation.run(monitor);
return repositoryPath;
} catch (final InvocationTargetException e) {
throw getTeamException(e);
}
}
}
private Map<URIish, Map<String, Set<ProjectReference>>> parseReferenceStrings()
throws TeamException {
final Map<URIish, Map<String, Set<ProjectReference>>> repositories = new LinkedHashMap<URIish, Map<String, Set<ProjectReference>>>();
for (final String reference : referenceStrings) {
try {
final ProjectReference projectReference = new ProjectReference(
reference);
Map<String, Set<ProjectReference>> repositoryBranches = repositories
.get(projectReference.getRepository());
if (repositoryBranches == null) {
repositoryBranches = new HashMap<String, Set<ProjectReference>>();
repositories.put(projectReference.getRepository(),
repositoryBranches);
}
Set<ProjectReference> projectReferences = repositoryBranches
.get(projectReference.getBranch());
if (projectReferences == null) {
projectReferences = new LinkedHashSet<ProjectReference>();
repositoryBranches.put(projectReference.getBranch(),
projectReferences);
}
projectReferences.add(projectReference);
} catch (final IllegalArgumentException e) {
throw new TeamException(reference, e);
} catch (final URISyntaxException e) {
throw new TeamException(reference, e);
}
}
return repositories;
}
/**
* @param gitUrl
* @param branch
* the branch to check out
* @param allBranches
* all branches which should be checked out for this gitUrl
* @return the directory where the project should be checked out
*/
private static IPath getWorkingDir(URIish gitUrl, String branch,
Set<String> allBranches) {
final IPath workspaceLocation = ResourcesPlugin.getWorkspace()
.getRoot().getRawLocation();
final String humanishName = gitUrl.getHumanishName();
String extendedName;
if (allBranches.size() == 1 || branch.equals(Constants.MASTER))
extendedName = humanishName;
else
extendedName = humanishName + "_" + branch; //$NON-NLS-1$
final IPath workDir = workspaceLocation.append(extendedName);
return workDir;
}
private static File findConfiguredRepository(URIish gitUrl) {
for (String repoDir : getRepositoryUtil().getConfiguredRepositories()) {
File repoDirFile = new File(repoDir);
if (repositoryAlreadyExistsForUrl(repoDirFile, gitUrl))
return repoDirFile;
}
return null;
}
private static boolean repositoryAlreadyExistsForUrl(File repositoryPath,
URIish gitUrl) {
if (repositoryPath.exists()) {
Repository existingRepository;
try {
existingRepository = FileRepositoryBuilder
.create(repositoryPath);
} catch (IOException e) {
return false;
}
try {
boolean exists = containsRemoteForUrl(
existingRepository.getConfig(), gitUrl);
return exists;
} catch (URISyntaxException e) {
return false;
} finally {
existingRepository.close();
}
}
return false;
}
private static boolean containsRemoteForUrl(Config config, URIish url)
throws URISyntaxException {
Set<String> remotes = config.getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
for (String remote : remotes) {
String remoteUrl = config.getString(
ConfigConstants.CONFIG_REMOTE_SECTION,
remote,
ConfigConstants.CONFIG_KEY_URL);
URIish existingUrl = new URIish(remoteUrl);
if (existingUrl.equals(url))
return true;
// try URLs without user name, since often project sets contain
// anonymous URLs, and remote URL might be anonymous as well
URIish anonExistingUrl = existingUrl.setUser(null);
URIish anonUrl = url.setUser(null);
if (anonExistingUrl.equals(anonUrl))
return true;
}
return false;
}
private List<IProject> importProjects(final Set<ProjectReference> projects,
final IPath workDir, final File repositoryPath,
final IProgressMonitor monitor) throws TeamException {
try {
List<IProject> importedProjects = new ArrayList<IProject>();
// import projects from the current repository to workspace
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IWorkspaceRoot root = workspace.getRoot();
for (final ProjectReference projectToImport : projects) {
final IPath projectDir = workDir.append(projectToImport
.getProjectDir());
final IProjectDescription projectDescription = workspace
.loadProjectDescription(projectDir
.append(IProjectDescription.DESCRIPTION_FILE_NAME));
final IProject project = root.getProject(projectDescription
.getName());
if (!project.exists()) {
project.create(projectDescription, monitor);
importedProjects.add(project);
}
project.open(monitor);
final ConnectProviderOperation connectProviderOperation = new ConnectProviderOperation(
project, repositoryPath);
connectProviderOperation.execute(monitor);
}
return importedProjects;
} catch (final CoreException e) {
throw TeamException.asTeamException(e);
}
}
private static TeamException getTeamException(final Throwable throwable) {
Throwable current = throwable;
while (current.getCause() != null)
current = current.getCause();
return new TeamException(current.getMessage(), current);
}
private static RepositoryUtil getRepositoryUtil() {
return Activator.getDefault().getRepositoryUtil();
}
}