/******************************************************************************* * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Google Inc. * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua> * Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com> * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com> * * 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.op; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; 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.MultiStatus; 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.egit.core.Activator; import org.eclipse.egit.core.GitProvider; import org.eclipse.egit.core.JobFamilies; import org.eclipse.egit.core.RepositoryUtil; import org.eclipse.egit.core.internal.CoreText; import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry; import org.eclipse.egit.core.internal.job.JobUtil; import org.eclipse.egit.core.internal.trace.GitTraceLocation; import org.eclipse.egit.core.project.GitProjectData; import org.eclipse.egit.core.project.RepositoryFinder; import org.eclipse.egit.core.project.RepositoryMapping; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.RepositoryProvider; /** * Connects Eclipse to an existing Git repository */ public class ConnectProviderOperation implements IEGitOperation { private final Map<IProject, File> projects = new LinkedHashMap<IProject, File>(); private boolean refreshResources = true; /** * Create a new connection operation to execute within the workspace. * <p> * Uses <code>.git</code> as a default relative path to repository. * @see #ConnectProviderOperation(IProject, File) * * @param proj * the project to connect to the Git team provider. */ public ConnectProviderOperation(final IProject proj) { this(proj, proj.getLocation().append(Constants.DOT_GIT).toFile()); } /** * Create a new connection operation to execute within the workspace. * * @param proj * the project to connect to the Git team provider. * @param pathToRepo * absolute path to the repository */ public ConnectProviderOperation(final IProject proj, File pathToRepo) { this.projects.put(proj, pathToRepo); } /** * Create a new connection operation to execute within the workspace. * * @param projects * the projects to connect to the Git team provider. */ public ConnectProviderOperation(final Map<IProject, File> projects) { this.projects.putAll(projects); } @Override public void execute(IProgressMonitor m) throws CoreException { SubMonitor progress = SubMonitor.convert(m, CoreText.ConnectProviderOperation_connecting, projects.size()); MultiStatus ms = new MultiStatus(Activator.getPluginId(), 0, CoreText.ConnectProviderOperation_ConnectErrors, null); for (Entry<IProject, File> entry : projects.entrySet()) { connectProject(entry, ms, progress.newChild(1)); } if (!ms.isOK()) { throw new CoreException(ms); } } private void connectProject(Entry<IProject, File> entry, MultiStatus ms, IProgressMonitor monitor) throws CoreException { IProject project = entry.getKey(); String taskName = NLS.bind( CoreText.ConnectProviderOperation_ConnectingProject, project.getName()); SubMonitor subMon = SubMonitor.convert(monitor, taskName, 100); if (GitTraceLocation.CORE.isActive()) { GitTraceLocation.getTrace() .trace(GitTraceLocation.CORE.getLocation(), taskName); } RepositoryFinder finder = new RepositoryFinder(project); finder.setFindInChildren(false); Collection<RepositoryMapping> repos = finder.find(subMon.newChild(50)); if (repos.isEmpty()) { ms.add(Activator.error(NLS.bind( CoreText.ConnectProviderOperation_NoRepositoriesError, project.getName()), null)); return; } RepositoryMapping actualMapping = findActualRepository(repos, entry.getValue()); if (actualMapping == null) { ms.add(Activator.error(NLS.bind( CoreText.ConnectProviderOperation_UnexpectedRepositoryError, new Object[] { project.getName(), entry.getValue().toString(), repos.toString() }), null)); return; } GitProjectData projectData = new GitProjectData(project); try { projectData.setRepositoryMappings(Arrays.asList(actualMapping)); projectData.store(); GitProjectData.add(project, projectData); } catch (CoreException ce) { ms.add(ce.getStatus()); deleteGitProvider(ms, project); return; } catch (RuntimeException ce) { ms.add(Activator.error(ce.getMessage(), ce)); deleteGitProvider(ms, project); return; } RepositoryProvider.map(project, GitProvider.ID); if (refreshResources) { touchGitResources(project, subMon.newChild(10)); project.refreshLocal(IResource.DEPTH_INFINITE, subMon.newChild(30)); IndexDiffCacheEntry cacheEntry = org.eclipse.egit.core.Activator .getDefault().getIndexDiffCache() .getIndexDiffCacheEntry(actualMapping.getRepository()); if (cacheEntry != null) { cacheEntry.refresh(); } } else { subMon.worked(40); } autoIgnoreDerivedResources(project, subMon.newChild(10)); autoIgnoreWorkspaceMetaData( actualMapping.getRepository().getDirectory().toPath()); } /** * Touches all descendants named ".git" so that they'll be included in a * subsequent resource delta. * * @param project * to process * @param monitor * for progress reporting and cancellation, may be {@code null} * if neither is desired */ private void touchGitResources(IProject project, IProgressMonitor monitor) { final SubMonitor progress = SubMonitor.convert(monitor, 1); try { project.accept(new IResourceProxyVisitor() { @Override public boolean visit(IResourceProxy resource) throws CoreException { int type = resource.getType(); if ((type == IResource.FILE || type == IResource.FOLDER) && Constants.DOT_GIT.equals(resource.getName())) { progress.setWorkRemaining(2); resource.requestResource().touch(progress.newChild(1)); return false; } return true; } }, IResource.NONE); } catch (CoreException e) { Activator.logError(e.getMessage(), e); } } private void deleteGitProvider(MultiStatus ms, IProject project) { try { GitProjectData.delete(project); } catch (IOException e) { ms.add(Activator.error(e.getMessage(), e)); } } private void autoIgnoreDerivedResources(IProject project, IProgressMonitor monitor) throws CoreException { if (!Activator.autoIgnoreDerived()) { return; } List<IPath> paths = findDerivedResources(project); if (paths.size() > 0) { IgnoreOperation ignoreOp = new IgnoreOperation(paths); ignoreOp.execute(monitor); } } /** * Auto-ignore the .metadata and .recommenders folders located in the * workspace root if the workspace is in the working tree of a git * repository (which is not recommended) * * @param gitDir * path of git directory containing the git repository */ private static void autoIgnoreWorkspaceMetaData(java.nio.file.Path gitDir) { java.nio.file.Path workspaceRoot = ResourcesPlugin.getWorkspace() .getRoot().getLocation().toFile().toPath(); try (Repository r = FileRepositoryBuilder.create(gitDir.toFile())) { if (!r.isBare() && workspaceRoot.startsWith(r.getWorkTree().toPath())) { Collection<IPath> ignoredPaths = buildIgnoredPathsList( workspaceRoot, ".metadata", //$NON-NLS-1$ ".recommenders"); //$NON-NLS-1$ JobUtil.scheduleUserJob(new IgnoreOperation(ignoredPaths), CoreText.ConnectProviderOperation_autoIgnoreMetaData, JobFamilies.AUTO_IGNORE); } } catch (IOException e) { Activator.logError(e.getMessage(), e); } } private static Collection<IPath> buildIgnoredPathsList( java.nio.file.Path workspaceRoot, String... metaDataDirectoryNames) { Collection<IPath> ignoredPaths = new HashSet<IPath>(); for (String m : metaDataDirectoryNames) { Path metaData = new Path( workspaceRoot.resolve(m).toAbsolutePath().toString()); try { if (RepositoryUtil.canBeAutoIgnored(metaData)) { ignoredPaths.add(metaData); } } catch (IOException e) { Activator.logError(e.getMessage(), e); } } return ignoredPaths; } private List<IPath> findDerivedResources(IContainer c) throws CoreException { List<IPath> derived = new ArrayList<IPath>(); IResource[] members = c.members(IContainer.INCLUDE_HIDDEN); for (IResource r : members) { if (r.isDerived()) derived.add(r.getLocation()); else if (r instanceof IContainer) derived.addAll(findDerivedResources((IContainer) r)); } return derived; } @Override public ISchedulingRule getSchedulingRule() { Set<IProject> projectSet = projects.keySet(); return new MultiRule(projectSet.toArray(new IProject[projectSet.size()])); } /** * @param repos * available repositories * @param suggestedRepo * relative path to git repository * @return a repository mapping which corresponds to a suggested repository * location, <code>null</code> otherwise */ @Nullable private RepositoryMapping findActualRepository( Collection<RepositoryMapping> repos, File suggestedRepo) { File path = Path.fromOSString(suggestedRepo.getPath()).toFile(); for (RepositoryMapping rm : repos) { IPath other = rm.getGitDirAbsolutePath(); if (other == null) { continue; } if (path.equals(other.toFile())) { return rm; } } return null; } /** * @param refresh * true to refresh resources after connect operation (default) */ public void setRefreshResources(boolean refresh) { this.refreshResources = refresh; } }