Do not refresh projects that use a PessimisticResourceRuleFactory
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / internal / util / ProjectUtil.java
blobad01f32f99b7e4b85826731e173f04dff86c547b
1 /*******************************************************************************
2 * Copyright (c) 2004, 2008 IBM Corporation and others.
3 * Copyright (C) 2007, Martin Oberhuber (martin.oberhuber@windriver.com)
4 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5 * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
6 * Copyright (C) 2012, 2013 Robin Stocker <robin@nibor.org>
7 * Copyright (C) 2015, Stephan Hackstedt <stephan.hackstedt@googlemail.com>
8 * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
10 * All rights reserved. This program and the accompanying materials
11 * are made available under the terms of the Eclipse Public License 2.0
12 * which accompanies this distribution, and is available at
13 * https://www.eclipse.org/legal/epl-2.0/
15 * SPDX-License-Identifier: EPL-2.0
16 *******************************************************************************/
17 package org.eclipse.egit.core.internal.util;
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Comparator;
25 import java.util.HashSet;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Set;
30 import org.eclipse.core.resources.IContainer;
31 import org.eclipse.core.resources.IProject;
32 import org.eclipse.core.resources.IProjectDescription;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IResourceRuleFactory;
35 import org.eclipse.core.resources.IWorkspaceRoot;
36 import org.eclipse.core.resources.ResourcesPlugin;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IPath;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.NullProgressMonitor;
41 import org.eclipse.core.runtime.Path;
42 import org.eclipse.core.runtime.SubMonitor;
43 import org.eclipse.egit.core.internal.CoreText;
44 import org.eclipse.egit.core.project.RepositoryMapping;
45 import org.eclipse.jgit.annotations.NonNull;
46 import org.eclipse.jgit.lib.Constants;
47 import org.eclipse.jgit.lib.Repository;
48 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
49 import org.eclipse.jgit.util.FS;
50 import org.eclipse.jgit.util.FileUtils;
51 import org.eclipse.osgi.util.NLS;
53 /**
54 * This class contains utility methods related to projects
55 * TODO: rename to RefreshUtil or ResourceUtil?
57 public class ProjectUtil {
59 /**
60 * The name of the folder containing metadata information for the workspace.
62 public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
64 /**
65 * The method returns all valid open projects contained in the given Git
66 * repository. A project is considered as valid if the .project file exists.
67 * @see ProjectUtil#refreshValidProjects(IProject[], IProgressMonitor)
68 * @param repository
69 * @return valid open projects
70 * @throws CoreException
72 public static IProject[] getValidOpenProjects(Repository repository)
73 throws CoreException {
74 if (repository == null || repository.isBare()) {
75 return new IProject[0];
77 final IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
78 .getProjects();
79 List<IProject> result = new ArrayList<>();
80 final Path repositoryPath = new Path(
81 repository.getWorkTree().getAbsolutePath());
82 for (IProject p : projects) {
83 IPath projectLocation = p.getLocation();
84 if (!p.isOpen() || projectLocation == null
85 || !repositoryPath.isPrefixOf(projectLocation))
86 continue;
87 IPath projectFilePath = projectLocation
88 .append(IProjectDescription.DESCRIPTION_FILE_NAME);
89 if (projectFilePath.toFile().exists()) {
90 result.add(p);
93 return result.toArray(new IProject[result.size()]);
96 /**
97 * The method refreshes the given projects. Projects with missing .project
98 * file are deleted. The method should be called in the following flow:<br>
99 * <ol>
100 * <li>Call {@link ProjectUtil#getValidOpenProjects(Repository)}
101 * <li>Perform a workdir checkout (e.g. branch, reset)
102 * <li>Call
103 * {@link ProjectUtil#refreshValidProjects(IProject[], IProgressMonitor)}
104 * </ol>
106 * @param projects
107 * list of valid projects before workdir checkout.
108 * @param monitor
109 * @throws CoreException
111 public static void refreshValidProjects(IProject[] projects,
112 IProgressMonitor monitor) throws CoreException {
113 refreshValidProjects(projects, true, monitor);
117 * The method refreshes the given projects. Projects with missing .project
118 * file are deleted. The method should be called in the following flow:<br>
119 * <ol>
120 * <li>Call {@link ProjectUtil#getValidOpenProjects(Repository)}
121 * <li>Perform a workdir checkout (e.g. branch, reset)
122 * <li>Call
123 * {@link ProjectUtil#refreshValidProjects(IProject[], IProgressMonitor)}
124 * </ol>
126 * @param projects
127 * list of valid projects before workdir checkout.
128 * @param delete
129 * true to delete projects, false to close them
130 * @param monitor
132 * @throws CoreException
134 public static void refreshValidProjects(IProject[] projects, boolean delete,
135 IProgressMonitor monitor) throws CoreException {
136 SubMonitor progress = SubMonitor.convert(monitor,
137 CoreText.ProjectUtil_refreshingProjects, projects.length);
138 IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace()
139 .getRuleFactory();
140 for (IProject p : projects) {
141 if (progress.isCanceled())
142 break;
143 IPath projectLocation = p.getLocation();
144 if (projectLocation == null
145 || !p.contains(ruleFactory.refreshRule(p))) {
146 progress.worked(1);
147 continue;
149 String projectFilePath = projectLocation
150 .append(IProjectDescription.DESCRIPTION_FILE_NAME)
151 .toOSString();
152 File projectFile = new File(projectFilePath);
153 if (projectFile.exists())
154 p.refreshLocal(IResource.DEPTH_INFINITE, progress.newChild(1));
155 else if (delete)
156 p.delete(false, true, progress.newChild(1));
157 else
158 closeMissingProject(p, projectFile, progress.newChild(1));
163 * Close a project that has already been deleted on disk. This will fall
164 * back to deleting the project if it cannot be successfully closed.
165 * <p>
166 * Closing a missing project involves creating a temporary '.project' file
167 * since only existing projects can be closed
169 * @param p
170 * @param projectFile
171 * @param monitor
172 * @throws CoreException
174 static void closeMissingProject(IProject p, File projectFile,
175 IProgressMonitor monitor) throws CoreException {
176 SubMonitor progress = SubMonitor.convert(monitor, 1);
177 // Don't close/delete if already closed
178 if (p.exists() && !p.isOpen())
179 return;
181 // Create temporary .project file so it can be closed
182 boolean closeFailed = false;
183 File projectRoot = projectFile.getParentFile();
184 if (!projectRoot.isFile()) {
185 boolean hasRoot = projectRoot.exists();
186 try {
187 if (!hasRoot)
188 FileUtils.mkdirs(projectRoot, true);
189 if (projectFile.createNewFile())
190 p.close(progress.newChild(1));
191 else
192 closeFailed = true;
193 } catch (IOException e) {
194 closeFailed = true;
195 } finally {
196 // Clean up created .project file
197 try {
198 FileUtils.delete(projectFile, FileUtils.RETRY
199 | FileUtils.SKIP_MISSING);
200 } catch (IOException e) {
201 closeFailed = true;
203 // Clean up created folder
204 if (!hasRoot)
205 try {
206 FileUtils.delete(projectRoot, FileUtils.RETRY
207 | FileUtils.SKIP_MISSING | FileUtils.RECURSIVE);
208 } catch (IOException e) {
209 closeFailed = true;
212 } else
213 closeFailed = true;
214 // Delete projects that can't be closed
215 if (closeFailed)
216 p.delete(false, true, progress.newChild(1));
220 * The method refreshes resources
222 * @param resources
223 * resources to refresh
224 * @param monitor
225 * @throws CoreException
227 public static void refreshResources(IResource[] resources,
228 IProgressMonitor monitor) throws CoreException {
229 try {
230 SubMonitor progress = SubMonitor.convert(monitor,
231 CoreText.ProjectUtil_refreshing, resources.length);
232 for (IResource resource : resources) {
233 if (progress.isCanceled())
234 break;
235 resource.refreshLocal(IResource.DEPTH_INFINITE,
236 progress.newChild(1));
238 } finally {
239 monitor.done();
244 * Refresh the resources that are within the passed repository paths.
246 * @param repository
247 * @param relativePaths
248 * repository-relative paths to refresh
249 * @param monitor
250 * @throws CoreException
252 public static void refreshRepositoryResources(Repository repository,
253 Collection<String> relativePaths, IProgressMonitor monitor)
254 throws CoreException {
255 if (repository == null || repository.isBare()) {
256 return;
258 if (relativePaths.isEmpty() || relativePaths.contains("")) { //$NON-NLS-1$
259 refreshResources(getProjects(repository), monitor);
260 return;
263 IPath repositoryPath = new Path(repository.getWorkTree().getAbsolutePath());
264 IProject[] projects = null;
265 Set<IResource> resources = new LinkedHashSet<>();
266 for (String relativePath : relativePaths) {
267 IPath location = repositoryPath.append(relativePath);
268 IResource resource = ResourceUtil
269 .getResourceForLocation(location, false);
270 if (resource != null) {
271 // Resource exists for path, refresh it
272 resources.add(resource);
273 } else {
274 // Resource doesn't exist. Check if there are any projects
275 // contained in the path, we need to refresh them.
276 if (projects == null)
277 projects = getProjects(repository);
278 for (IProject project : projects) {
279 IPath projectLocation = project.getLocation();
280 if (projectLocation != null
281 && location.isPrefixOf(projectLocation))
282 resources.add(project);
286 refreshResources(resources.toArray(new IResource[0]), monitor);
290 * The method retrieves all accessible projects related to the given
291 * repository.
293 * @param repository
294 * to get the projects of
295 * @return list of projects, with nested projects first.
297 public static IProject[] getProjects(Repository repository) {
298 if (repository == null || repository.isBare()) {
299 return new IProject[0];
301 List<IProject> result = new ArrayList<>();
302 for (IProject project : getProjectsUnderPath(
303 new Path(repository.getWorkTree().getAbsolutePath()))) {
304 RepositoryMapping mapping = RepositoryMapping.getMapping(project);
305 if (mapping != null) {
306 result.add(project);
309 return result.toArray(new IProject[result.size()]);
313 * The method returns all projects containing at least one of the given
314 * paths.
316 * @param repository
317 * the repository who's working tree is used as base for lookup
318 * @param fileList
319 * the list of files/directories to lookup
320 * @return valid projects containing one of the paths
321 * @throws CoreException
323 public static IProject[] getProjectsContaining(Repository repository,
324 Collection<String> fileList) throws CoreException {
325 if (repository == null || repository.isBare()) {
326 return new IProject[0];
328 Set<IProject> result = new LinkedHashSet<>();
329 File workTree = repository.getWorkTree();
331 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
332 IProject[] projects = getProjectsForContainerMatch(root);
334 for (String member : fileList) {
335 File file = new File(workTree, member);
337 for (IProject prj : projects) {
338 if (checkContainerMatch(prj, file.getAbsolutePath())) {
339 result.add(prj);
340 break;
345 return result.toArray(new IProject[result.size()]);
349 * Looks up the IProject containing the given file, if available. This is
350 * done by path comparison, which is very cheap compared to
351 * IWorkspaceRoot.findContainersForLocationURI(). If no project is found the
352 * code returns the {@link IWorkspaceRoot} or the file is inside the
353 * workspace.
355 * @param file
356 * the path to lookup a container for
357 * @return the IProject or IWorkspaceRoot or <code>null</code> if not found.
359 public static IContainer findProjectOrWorkspaceRoot(File file) {
360 String absFile = file.getAbsolutePath();
361 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
362 IProject[] allProjects = getProjectsForContainerMatch(root);
364 for (IProject prj : allProjects)
365 if (checkContainerMatch(prj, absFile))
366 return prj;
368 if (checkContainerMatch(root, absFile))
369 return root;
371 return null;
374 private static IProject[] getProjectsForContainerMatch(IWorkspaceRoot root) {
375 IProject[] allProjects = root.getProjects();
377 // Sorting makes us look into nested projects first
378 Arrays.sort(allProjects, new Comparator<IProject>() {
379 @Override
380 public int compare(IProject o1, IProject o2) {
381 IPath l1 = o1.getLocation();
382 IPath l2 = o2.getLocation();
383 if (l1 != null && l2 != null)
384 return -l1.toFile().compareTo(l2.toFile());
385 else if (l1 != null)
386 return -1;
387 else if (l2 != null)
388 return 1;
389 else
390 return 0;
394 return allProjects;
397 private static boolean checkContainerMatch(IContainer container,
398 String absFile) {
399 IPath location = container.getLocation();
400 if (location != null) {
401 String absPrj = location.toFile().getAbsolutePath();
402 if (absPrj.equals(absFile))
403 return true;
404 if (absPrj.length() < absFile.length()) {
405 char sepChar = absFile.charAt(absPrj.length());
406 if (sepChar == File.separatorChar && absFile.startsWith(absPrj))
407 return true;
410 return false;
414 * Find projects located under the given path.
416 * @param path
417 * absolute path under which to look for projects
418 * @return projects located under the given path
420 public static IProject[] getProjectsUnderPath(@NonNull final IPath path) {
421 IProject[] allProjects = getProjectsForContainerMatch(ResourcesPlugin
422 .getWorkspace().getRoot());
423 Set<IProject> projects = new HashSet<>();
424 for (IProject p : allProjects) {
425 IPath loc = p.getLocation();
426 if (loc != null && path.isPrefixOf(loc)) {
427 projects.add(p);
430 return projects.toArray(new IProject[projects.size()]);
434 * Find directories containing .project files recursively starting at given
435 * directory
437 * @param files
438 * the collection to add the found projects to
439 * @param directory
440 * where to search for project files
441 * @param searchNested
442 * whether to search for nested projects or not
443 * @param monitor
444 * @return true if projects files found, false otherwise
446 public static boolean findProjectFiles(final Collection<File> files,
447 final File directory, boolean searchNested,
448 final IProgressMonitor monitor) {
449 return findProjectFiles(files, directory, searchNested, null, monitor);
452 private static boolean findProjectFiles(final Collection<File> files,
453 final File directory, final boolean searchNested,
454 final Set<String> visistedDirs, final IProgressMonitor monitor) {
455 if (directory == null)
456 return false;
458 if (directory.getName().equals(Constants.DOT_GIT)
459 && FileKey.isGitRepository(directory, FS.DETECTED))
460 return false;
462 IProgressMonitor pm = monitor;
463 if (pm == null)
464 pm = new NullProgressMonitor();
465 else if (pm.isCanceled())
466 return false;
468 pm.subTask(NLS.bind(CoreText.ProjectUtil_taskCheckingDirectory,
469 directory.getPath()));
471 final File[] contents = directory.listFiles();
472 if (contents == null || contents.length == 0)
473 return false;
475 Set<String> directoriesVisited;
476 // Initialize recursion guard for recursive symbolic links
477 if (visistedDirs == null) {
478 directoriesVisited = new HashSet<>();
479 directoriesVisited.add(directory.getAbsolutePath());
480 } else
481 directoriesVisited = visistedDirs;
483 // first look for project description files
484 boolean foundProject = false;
485 final String dotProject = IProjectDescription.DESCRIPTION_FILE_NAME;
486 for (int i = 0; i < contents.length; i++) {
487 File file = contents[i];
488 if (file.isFile() && file.getName().equals(dotProject)) {
489 files.add(file);
490 foundProject = true;
493 if (foundProject && !searchNested)
494 return true;
495 // recurse into sub-directories (even when project was found above, for nested projects)
496 for (int i = 0; i < contents.length; i++) {
497 // Skip non-directories
498 if (!contents[i].isDirectory())
499 continue;
500 // Skip .metadata folders
501 if (contents[i].getName().equals(METADATA_FOLDER))
502 continue;
503 String path = contents[i].getAbsolutePath();
504 if (!directoriesVisited.add(path))
505 // already been here --> do not recurse
506 continue;
507 findProjectFiles(files, contents[i], searchNested,
508 directoriesVisited, pm);
510 return true;