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
;
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
;
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
;
54 * This class contains utility methods related to projects
55 * TODO: rename to RefreshUtil or ResourceUtil?
57 public class ProjectUtil
{
60 * The name of the folder containing metadata information for the workspace.
62 public static final String METADATA_FOLDER
= ".metadata"; //$NON-NLS-1$
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)
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()
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
))
87 IPath projectFilePath
= projectLocation
88 .append(IProjectDescription
.DESCRIPTION_FILE_NAME
);
89 if (projectFilePath
.toFile().exists()) {
93 return result
.toArray(new IProject
[result
.size()]);
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>
100 * <li>Call {@link ProjectUtil#getValidOpenProjects(Repository)}
101 * <li>Perform a workdir checkout (e.g. branch, reset)
103 * {@link ProjectUtil#refreshValidProjects(IProject[], IProgressMonitor)}
107 * list of valid projects before workdir checkout.
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>
120 * <li>Call {@link ProjectUtil#getValidOpenProjects(Repository)}
121 * <li>Perform a workdir checkout (e.g. branch, reset)
123 * {@link ProjectUtil#refreshValidProjects(IProject[], IProgressMonitor)}
127 * list of valid projects before workdir checkout.
129 * true to delete projects, false to close them
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()
140 for (IProject p
: projects
) {
141 if (progress
.isCanceled())
143 IPath projectLocation
= p
.getLocation();
144 if (projectLocation
== null
145 || !p
.contains(ruleFactory
.refreshRule(p
))) {
149 String projectFilePath
= projectLocation
150 .append(IProjectDescription
.DESCRIPTION_FILE_NAME
)
152 File projectFile
= new File(projectFilePath
);
153 if (projectFile
.exists())
154 p
.refreshLocal(IResource
.DEPTH_INFINITE
, progress
.newChild(1));
156 p
.delete(false, true, progress
.newChild(1));
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.
166 * Closing a missing project involves creating a temporary '.project' file
167 * since only existing projects can be closed
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())
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();
188 FileUtils
.mkdirs(projectRoot
, true);
189 if (projectFile
.createNewFile())
190 p
.close(progress
.newChild(1));
193 } catch (IOException e
) {
196 // Clean up created .project file
198 FileUtils
.delete(projectFile
, FileUtils
.RETRY
199 | FileUtils
.SKIP_MISSING
);
200 } catch (IOException e
) {
203 // Clean up created folder
206 FileUtils
.delete(projectRoot
, FileUtils
.RETRY
207 | FileUtils
.SKIP_MISSING
| FileUtils
.RECURSIVE
);
208 } catch (IOException e
) {
214 // Delete projects that can't be closed
216 p
.delete(false, true, progress
.newChild(1));
220 * The method refreshes resources
223 * resources to refresh
225 * @throws CoreException
227 public static void refreshResources(IResource
[] resources
,
228 IProgressMonitor monitor
) throws CoreException
{
230 SubMonitor progress
= SubMonitor
.convert(monitor
,
231 CoreText
.ProjectUtil_refreshing
, resources
.length
);
232 for (IResource resource
: resources
) {
233 if (progress
.isCanceled())
235 resource
.refreshLocal(IResource
.DEPTH_INFINITE
,
236 progress
.newChild(1));
244 * Refresh the resources that are within the passed repository paths.
247 * @param relativePaths
248 * repository-relative paths to refresh
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()) {
258 if (relativePaths
.isEmpty() || relativePaths
.contains("")) { //$NON-NLS-1$
259 refreshResources(getProjects(repository
), monitor
);
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
);
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
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) {
309 return result
.toArray(new IProject
[result
.size()]);
313 * The method returns all projects containing at least one of the given
317 * the repository who's working tree is used as base for lookup
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())) {
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
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
))
368 if (checkContainerMatch(root
, absFile
))
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
>() {
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());
397 private static boolean checkContainerMatch(IContainer container
,
399 IPath location
= container
.getLocation();
400 if (location
!= null) {
401 String absPrj
= location
.toFile().getAbsolutePath();
402 if (absPrj
.equals(absFile
))
404 if (absPrj
.length() < absFile
.length()) {
405 char sepChar
= absFile
.charAt(absPrj
.length());
406 if (sepChar
== File
.separatorChar
&& absFile
.startsWith(absPrj
))
414 * Find projects located under the given 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
)) {
430 return projects
.toArray(new IProject
[projects
.size()]);
434 * Find directories containing .project files recursively starting at given
438 * the collection to add the found projects to
440 * where to search for project files
441 * @param searchNested
442 * whether to search for nested projects or not
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)
458 if (directory
.getName().equals(Constants
.DOT_GIT
)
459 && FileKey
.isGitRepository(directory
, FS
.DETECTED
))
462 IProgressMonitor pm
= monitor
;
464 pm
= new NullProgressMonitor();
465 else if (pm
.isCanceled())
468 pm
.subTask(NLS
.bind(CoreText
.ProjectUtil_taskCheckingDirectory
,
469 directory
.getPath()));
471 final File
[] contents
= directory
.listFiles();
472 if (contents
== null || contents
.length
== 0)
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());
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
)) {
493 if (foundProject
&& !searchNested
)
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())
500 // Skip .metadata folders
501 if (contents
[i
].getName().equals(METADATA_FOLDER
))
503 String path
= contents
[i
].getAbsolutePath();
504 if (!directoriesVisited
.add(path
))
505 // already been here --> do not recurse
507 findProjectFiles(files
, contents
[i
], searchNested
,
508 directoriesVisited
, pm
);