refactor: simplify collection.toArray()
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / ConnectProviderOperation.java
blob8d9ffdb72970905369bc97aa7e77cf083f23a810
1 /*******************************************************************************
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3 * Copyright (C) 2008, Google Inc.
4 * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua>
5 * Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com>
6 * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
8 * All rights reserved. This program and the accompanying materials
9 * are made available under the terms of the Eclipse Public License 2.0
10 * which accompanies this distribution, and is available at
11 * https://www.eclipse.org/legal/epl-2.0/
13 * SPDX-License-Identifier: EPL-2.0
14 *******************************************************************************/
15 package org.eclipse.egit.core.op;
17 import java.io.File;
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
29 import org.eclipse.core.resources.IContainer;
30 import org.eclipse.core.resources.IProject;
31 import org.eclipse.core.resources.IResource;
32 import org.eclipse.core.resources.IResourceProxy;
33 import org.eclipse.core.resources.IResourceProxyVisitor;
34 import org.eclipse.core.resources.ResourcesPlugin;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.core.runtime.IProgressMonitor;
38 import org.eclipse.core.runtime.MultiStatus;
39 import org.eclipse.core.runtime.Path;
40 import org.eclipse.core.runtime.SubMonitor;
41 import org.eclipse.core.runtime.jobs.ISchedulingRule;
42 import org.eclipse.core.runtime.jobs.MultiRule;
43 import org.eclipse.egit.core.Activator;
44 import org.eclipse.egit.core.GitProvider;
45 import org.eclipse.egit.core.JobFamilies;
46 import org.eclipse.egit.core.RepositoryUtil;
47 import org.eclipse.egit.core.internal.CoreText;
48 import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
49 import org.eclipse.egit.core.internal.job.JobUtil;
50 import org.eclipse.egit.core.internal.trace.GitTraceLocation;
51 import org.eclipse.egit.core.project.GitProjectData;
52 import org.eclipse.egit.core.project.RepositoryFinder;
53 import org.eclipse.egit.core.project.RepositoryMapping;
54 import org.eclipse.jgit.annotations.Nullable;
55 import org.eclipse.jgit.lib.Constants;
56 import org.eclipse.jgit.lib.Repository;
57 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
58 import org.eclipse.osgi.util.NLS;
59 import org.eclipse.team.core.RepositoryProvider;
61 /**
62 * Connects Eclipse to an existing Git repository
64 public class ConnectProviderOperation implements IEGitOperation {
65 private final Map<IProject, File> projects = new LinkedHashMap<>();
67 private boolean refreshResources = true;
69 /**
70 * Create a new connection operation to execute within the workspace.
71 * <p>
72 * Uses <code>.git</code> as a default relative path to repository.
73 * @see #ConnectProviderOperation(IProject, File)
75 * @param proj
76 * the project to connect to the Git team provider.
78 public ConnectProviderOperation(final IProject proj) {
79 this(proj, proj.getLocation().append(Constants.DOT_GIT).toFile());
82 /**
83 * Create a new connection operation to execute within the workspace.
85 * @param proj
86 * the project to connect to the Git team provider.
87 * @param pathToRepo
88 * absolute path to the repository
90 public ConnectProviderOperation(final IProject proj, File pathToRepo) {
91 this.projects.put(proj, pathToRepo);
94 /**
95 * Create a new connection operation to execute within the workspace.
97 * @param projects
98 * the projects to connect to the Git team provider.
100 public ConnectProviderOperation(final Map<IProject, File> projects) {
101 this.projects.putAll(projects);
104 @Override
105 public void execute(IProgressMonitor m) throws CoreException {
106 SubMonitor progress = SubMonitor.convert(m,
107 CoreText.ConnectProviderOperation_connecting, projects.size());
108 MultiStatus ms = new MultiStatus(Activator.getPluginId(), 0,
109 CoreText.ConnectProviderOperation_ConnectErrors, null);
110 for (Entry<IProject, File> entry : projects.entrySet()) {
111 connectProject(entry, ms, progress.newChild(1));
113 if (!ms.isOK()) {
114 throw new CoreException(ms);
118 private void connectProject(Entry<IProject, File> entry, MultiStatus ms,
119 IProgressMonitor monitor) throws CoreException {
120 IProject project = entry.getKey();
122 String taskName = NLS.bind(
123 CoreText.ConnectProviderOperation_ConnectingProject,
124 project.getName());
125 SubMonitor subMon = SubMonitor.convert(monitor, taskName, 100);
127 if (GitTraceLocation.CORE.isActive()) {
128 GitTraceLocation.getTrace()
129 .trace(GitTraceLocation.CORE.getLocation(), taskName);
132 RepositoryFinder finder = new RepositoryFinder(project);
133 finder.setFindInChildren(false);
134 Collection<RepositoryMapping> repos = finder.find(subMon.newChild(50));
135 if (repos.isEmpty()) {
136 ms.add(Activator.error(NLS.bind(
137 CoreText.ConnectProviderOperation_NoRepositoriesError,
138 project.getName()), null));
139 return;
141 RepositoryMapping actualMapping = findActualRepository(repos,
142 entry.getValue());
143 if (actualMapping == null) {
144 ms.add(Activator.error(NLS.bind(
145 CoreText.ConnectProviderOperation_UnexpectedRepositoryError,
146 new Object[] { project.getName(),
147 entry.getValue().toString(), repos.toString() }),
148 null));
149 return;
151 GitProjectData projectData = new GitProjectData(project);
152 try {
153 projectData.setRepositoryMappings(Arrays.asList(actualMapping));
154 projectData.store();
155 GitProjectData.add(project, projectData);
156 } catch (CoreException ce) {
157 ms.add(ce.getStatus());
158 deleteGitProvider(ms, project);
159 return;
160 } catch (RuntimeException ce) {
161 ms.add(Activator.error(ce.getMessage(), ce));
162 deleteGitProvider(ms, project);
163 return;
165 RepositoryProvider.map(project, GitProvider.ID);
167 IPath gitPath = actualMapping.getGitDirAbsolutePath();
168 if (refreshResources) {
169 touchGitResources(project, subMon.newChild(10));
170 project.refreshLocal(IResource.DEPTH_INFINITE, subMon.newChild(30));
171 if (gitPath != null) {
172 try {
173 Repository repository = org.eclipse.egit.core.Activator
174 .getDefault().getRepositoryCache()
175 .lookupRepository(gitPath.toFile());
176 IndexDiffCacheEntry cacheEntry = org.eclipse.egit.core.Activator
177 .getDefault().getIndexDiffCache()
178 .getIndexDiffCacheEntry(repository);
179 if (cacheEntry != null) {
180 cacheEntry.refresh();
182 } catch (IOException e) {
183 Activator.logError(e.getMessage(), e);
186 } else {
187 subMon.worked(40);
190 autoIgnoreDerivedResources(project, subMon.newChild(10));
191 if (gitPath != null) {
192 autoIgnoreWorkspaceMetaData(gitPath.toFile());
197 * Touches all descendants named ".git" so that they'll be included in a
198 * subsequent resource delta.
200 * @param project
201 * to process
202 * @param monitor
203 * for progress reporting and cancellation, may be {@code null}
204 * if neither is desired
206 private void touchGitResources(IProject project, IProgressMonitor monitor) {
207 final SubMonitor progress = SubMonitor.convert(monitor, 1);
208 try {
209 project.accept(new IResourceProxyVisitor() {
210 @Override
211 public boolean visit(IResourceProxy resource)
212 throws CoreException {
213 int type = resource.getType();
214 if ((type == IResource.FILE || type == IResource.FOLDER)
215 && Constants.DOT_GIT.equals(resource.getName())) {
216 progress.setWorkRemaining(2);
217 resource.requestResource().touch(progress.newChild(1));
218 return false;
220 return true;
222 }, IResource.NONE);
223 } catch (CoreException e) {
224 Activator.logError(e.getMessage(), e);
228 private void deleteGitProvider(MultiStatus ms, IProject project) {
229 try {
230 GitProjectData.delete(project);
231 } catch (IOException e) {
232 ms.add(Activator.error(e.getMessage(), e));
236 private void autoIgnoreDerivedResources(IProject project,
237 IProgressMonitor monitor) throws CoreException {
238 if (!Activator.autoIgnoreDerived()) {
239 return;
241 List<IPath> paths = findDerivedResources(project);
242 if (paths.size() > 0) {
243 IgnoreOperation ignoreOp = new IgnoreOperation(paths);
244 ignoreOp.execute(monitor);
249 * Auto-ignore the .metadata and .recommenders folders located in the
250 * workspace root if the workspace is in the working tree of a git
251 * repository (which is not recommended)
253 * @param gitDir
254 * the .git directory containing the repository metadata
256 private static void autoIgnoreWorkspaceMetaData(File gitDir) {
257 java.nio.file.Path workspaceRoot = ResourcesPlugin.getWorkspace()
258 .getRoot().getLocation().toFile().toPath();
259 try (Repository r = FileRepositoryBuilder.create(gitDir)) {
260 if (!r.isBare()
261 && workspaceRoot.startsWith(r.getWorkTree().toPath())) {
262 Collection<IPath> ignoredPaths = buildIgnoredPathsList(
263 workspaceRoot, ".metadata", //$NON-NLS-1$
264 ".recommenders"); //$NON-NLS-1$
265 JobUtil.scheduleUserJob(new IgnoreOperation(ignoredPaths),
266 CoreText.ConnectProviderOperation_autoIgnoreMetaData,
267 JobFamilies.AUTO_IGNORE);
269 } catch (IOException e) {
270 Activator.logError(e.getMessage(), e);
274 private static Collection<IPath> buildIgnoredPathsList(
275 java.nio.file.Path workspaceRoot,
276 String... metaDataDirectoryNames) {
277 Collection<IPath> ignoredPaths = new HashSet<>();
278 for (String m : metaDataDirectoryNames) {
279 Path metaData = new Path(
280 workspaceRoot.resolve(m).toAbsolutePath().toString());
281 try {
282 if (RepositoryUtil.canBeAutoIgnored(metaData)) {
283 ignoredPaths.add(metaData);
285 } catch (IOException e) {
286 Activator.logError(e.getMessage(), e);
289 return ignoredPaths;
292 private List<IPath> findDerivedResources(IContainer c)
293 throws CoreException {
294 List<IPath> derived = new ArrayList<>();
295 IResource[] members = c.members(IContainer.INCLUDE_HIDDEN);
296 for (IResource r : members) {
297 if (r.isDerived())
298 derived.add(r.getLocation());
299 else if (r instanceof IContainer)
300 derived.addAll(findDerivedResources((IContainer) r));
302 return derived;
305 @Override
306 public ISchedulingRule getSchedulingRule() {
307 Set<IProject> projectSet = projects.keySet();
308 return new MultiRule(projectSet.toArray(new IProject[0]));
312 * @param repos
313 * available repositories
314 * @param suggestedRepo
315 * relative path to git repository
316 * @return a repository mapping which corresponds to a suggested repository
317 * location, <code>null</code> otherwise
319 @Nullable
320 private RepositoryMapping findActualRepository(
321 Collection<RepositoryMapping> repos, File suggestedRepo) {
322 File path = Path.fromOSString(suggestedRepo.getPath()).toFile();
323 for (RepositoryMapping rm : repos) {
324 IPath other = rm.getGitDirAbsolutePath();
325 if (other == null) {
326 continue;
328 if (path.equals(other.toFile())) {
329 return rm;
332 return null;
336 * @param refresh
337 * true to refresh resources after connect operation (default)
339 public void setRefreshResources(boolean refresh) {
340 this.refreshResources = refresh;