1 /*******************************************************************************
2 * Copyright (C) 2007, IBM Corporation and others
3 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
4 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Copyright (C) 2008, Google Inc.
7 * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
8 * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
9 * Copyright (C) 2011, Christian Halstrick <christian.halstrick@sap.com>
10 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
12 * All rights reserved. This program and the accompanying materials
13 * are made available under the terms of the Eclipse Public License v1.0
14 * which accompanies this distribution, and is available at
15 * http://www.eclipse.org/legal/epl-v10.html
18 * Thomas Wolf <thomas.wolf@paranor.ch> - Factored out from DecoratableResourceAdapter
19 * and GitLightweightDecorator
20 *******************************************************************************/
21 package org
.eclipse
.egit
.ui
.internal
.resources
;
24 import java
.io
.IOException
;
25 import java
.nio
.file
.FileVisitResult
;
26 import java
.nio
.file
.FileVisitor
;
27 import java
.nio
.file
.Files
;
28 import java
.nio
.file
.Path
;
29 import java
.nio
.file
.Paths
;
30 import java
.nio
.file
.attribute
.BasicFileAttributes
;
31 import java
.util
.HashSet
;
34 import org
.eclipse
.core
.resources
.IContainer
;
35 import org
.eclipse
.core
.resources
.IResource
;
36 import org
.eclipse
.core
.runtime
.CoreException
;
37 import org
.eclipse
.core
.runtime
.IPath
;
38 import org
.eclipse
.egit
.core
.Activator
;
39 import org
.eclipse
.egit
.core
.internal
.indexdiff
.IndexDiffCacheEntry
;
40 import org
.eclipse
.egit
.core
.internal
.indexdiff
.IndexDiffData
;
41 import org
.eclipse
.egit
.core
.internal
.util
.ResourceUtil
;
42 import org
.eclipse
.egit
.ui
.internal
.resources
.IResourceState
.StagingState
;
43 import org
.eclipse
.jgit
.annotations
.NonNull
;
44 import org
.eclipse
.jgit
.annotations
.Nullable
;
45 import org
.eclipse
.jgit
.lib
.Constants
;
46 import org
.eclipse
.jgit
.lib
.Repository
;
49 * Factory for creating {@link IResourceState}s.
51 public class ResourceStateFactory
{
54 * {@link IResourceState} returned when no information can be retrieved. All
55 * boolean getters return {@code false}, and the
56 * {@link IResourceState.StagingState StagingState} is
57 * {@link IResourceState.StagingState#NOT_STAGED NOT_STAGED}.
60 public static final IResourceState UNKNOWN_STATE
= new ResourceState();
63 * Singleton {@link IResourceState} returned for ignored files.
66 private static final IResourceState IGNORED
= new ResourceState() {
69 public boolean isIgnored() {
75 private static final ResourceStateFactory INSTANCE
= new ResourceStateFactory();
78 * Retrieves the singleton instance of the {@link ResourceStateFactory}.
80 * @return the factory singleton
83 public static ResourceStateFactory
getInstance() {
88 * Returns the {@link IndexDiffData} for a given {@link IResource}, provided
89 * the resource exists and belongs to a git-tracked project.
92 * context to get the repository to get the index diff data from
93 * @return the IndexDiffData, or {@code null} if none.
96 public IndexDiffData
getIndexDiffDataOrNull(@Nullable IResource resource
) {
97 if (resource
== null || resource
.getType() == IResource
.ROOT
98 || !ResourceUtil
.isSharedWithGit(resource
)) {
101 Repository repository
= ResourceUtil
.getRepository(resource
);
102 return getIndexDiffDataOrNull(repository
);
106 * Returns the {@link IndexDiffData} for a given {@link File}, provided the
107 * file is in a git repository working tree.
110 * context to get the repository to get the index diff data from
111 * @return the IndexDiffData, or {@code null} if none.
114 public IndexDiffData
getIndexDiffDataOrNull(@Nullable File file
) {
118 File absoluteFile
= file
.getAbsoluteFile();
119 IPath path
= new org
.eclipse
.core
.runtime
.Path(absoluteFile
.getPath());
120 Repository repository
= ResourceUtil
.getRepository(path
);
121 return getIndexDiffDataOrNull(repository
);
125 * Returns the {@link IndexDiffData} for a given {@link Repository}.
128 * to get the index diff data from
129 * @return the IndexDiffData, or {@code null} if none.
132 private IndexDiffData
getIndexDiffDataOrNull(
133 @Nullable Repository repository
) {
134 if (repository
== null) {
136 } else if (repository
.isBare()) {
137 // For bare repository just return empty data
138 return new IndexDiffData();
140 IndexDiffCacheEntry diffCacheEntry
= Activator
.getDefault()
141 .getIndexDiffCache().getIndexDiffCacheEntry(repository
);
142 if (diffCacheEntry
== null) {
145 return diffCacheEntry
.getIndexDiff();
149 * Determines the repository state of the given {@link IResource}.
152 * to get the state for
153 * @return the state, {@link #UNKNOWN_STATE} if none can be determined.
156 public IResourceState
get(@Nullable IResource resource
) {
157 IndexDiffData indexDiffData
= getIndexDiffDataOrNull(resource
);
158 if (indexDiffData
== null || resource
== null) {
159 return UNKNOWN_STATE
;
161 return get(indexDiffData
, resource
);
165 * Determines the repository state of the given {@link File}.
168 * to get the state for
169 * @return the state, {@link #UNKNOWN_STATE} if none can be determined.
172 public IResourceState
get(@Nullable File file
) {
173 IndexDiffData indexDiffData
= getIndexDiffDataOrNull(file
);
174 if (indexDiffData
== null || file
== null) {
175 return UNKNOWN_STATE
;
177 return get(indexDiffData
, file
);
181 * Computes an {@link IResourceState} for the given {@link IResource} from
182 * the given {@link IndexDiffData}.
184 * @param indexDiffData
185 * to compute the state from
187 * to get the state of
191 public IResourceState
get(@NonNull IndexDiffData indexDiffData
,
192 @NonNull IResource resource
) {
193 IPath path
= resource
.getLocation();
195 return get(indexDiffData
, new ResourceItem(resource
));
197 return UNKNOWN_STATE
;
201 * Computes an {@link IResourceState} for the given {@link File} from the
202 * given {@link IndexDiffData}.
204 * @param indexDiffData
205 * to compute the state from
207 * to get the state of
211 public IResourceState
get(@NonNull IndexDiffData indexDiffData
,
212 @NonNull File file
) {
213 return get(indexDiffData
, new FileItem(file
));
217 * Computes an {@link IResourceState} for the given {@link FileSystemItem}
218 * from the given {@link IndexDiffData}.
220 * @param indexDiffData
221 * to compute the state from
223 * to get the state of
227 private IResourceState
get(@NonNull IndexDiffData indexDiffData
,
228 @NonNull FileSystemItem file
) {
229 IPath path
= file
.getAbsolutePath();
231 return UNKNOWN_STATE
;
233 Repository repository
= file
.getRepository();
234 if (repository
== null || repository
.isBare()) {
235 return UNKNOWN_STATE
;
237 File workTree
= repository
.getWorkTree();
238 String repoRelativePath
= path
.makeRelativeTo(
239 new org
.eclipse
.core
.runtime
.Path(workTree
.getAbsolutePath()))
241 if (repoRelativePath
.equals(path
.toString())) {
242 // Could not be made relative.
243 return UNKNOWN_STATE
;
245 if (file
.isContainer()) {
246 if (!repoRelativePath
.endsWith("/")) { //$NON-NLS-1$
247 repoRelativePath
+= '/';
249 if (ResourceUtil
.isSymbolicLink(repository
, repoRelativePath
)) {
250 // The Eclipse resource model handles a symlink to a folder like
251 // the container it refers to but git status handles the symlink
252 // source like a special file.
253 return extractFileProperties(indexDiffData
, repoRelativePath
);
255 return extractContainerProperties(indexDiffData
,
256 repoRelativePath
, file
);
259 return extractFileProperties(indexDiffData
, repoRelativePath
);
263 private @NonNull IResourceState
extractFileProperties(
264 @NonNull IndexDiffData indexDiffData
,
265 @NonNull String repoRelativePath
) {
266 Set
<String
> ignoredFiles
= indexDiffData
.getIgnoredNotInIndex();
267 boolean ignored
= ignoredFiles
.contains(repoRelativePath
)
268 || containsPrefixPath(ignoredFiles
, repoRelativePath
);
270 // Leave the rest at the default (false, NOT_STAGED)
273 ResourceState state
= new ResourceState();
274 Set
<String
> untracked
= indexDiffData
.getUntracked();
275 state
.setTracked(!untracked
.contains(repoRelativePath
));
277 Set
<String
> added
= indexDiffData
.getAdded();
278 Set
<String
> removed
= indexDiffData
.getRemoved();
279 Set
<String
> changed
= indexDiffData
.getChanged();
280 if (added
.contains(repoRelativePath
)) {
281 state
.setStagingState(StagingState
.ADDED
);
282 } else if (removed
.contains(repoRelativePath
)) {
283 state
.setStagingState(StagingState
.REMOVED
);
284 } else if (changed
.contains(repoRelativePath
)) {
285 state
.setStagingState(StagingState
.MODIFIED
);
287 state
.setStagingState(StagingState
.NOT_STAGED
);
291 Set
<String
> conflicting
= indexDiffData
.getConflicting();
292 state
.setConflicts(conflicting
.contains(repoRelativePath
));
295 Set
<String
> modified
= indexDiffData
.getModified();
296 state
.setDirty(modified
.contains(repoRelativePath
));
299 Set
<String
> missing
= indexDiffData
.getMissing();
300 state
.setMissing(missing
.contains(repoRelativePath
));
302 Set
<String
> assumeUnchanged
= indexDiffData
.getAssumeUnchanged();
303 state
.setAssumeUnchanged(assumeUnchanged
.contains(repoRelativePath
));
307 private @NonNull IResourceState
extractContainerProperties(
308 @NonNull IndexDiffData indexDiffData
,
309 @NonNull String repoRelativePath
,
310 @NonNull FileSystemItem directory
) {
311 Set
<String
> ignoredFiles
= indexDiffData
.getIgnoredNotInIndex();
312 boolean ignored
= containsPrefixPath(ignoredFiles
, repoRelativePath
)
313 || !directory
.hasContainerAnyFiles();
317 ResourceState state
= new ResourceState();
318 Set
<String
> untrackedFolders
= indexDiffData
.getUntrackedFolders();
320 !containsPrefixPath(untrackedFolders
, repoRelativePath
));
322 // containers are marked as staged whenever file was added, removed or
324 Set
<String
> changed
= new HashSet
<>(indexDiffData
.getChanged());
325 changed
.addAll(indexDiffData
.getAdded());
326 changed
.addAll(indexDiffData
.getRemoved());
327 if (containsPrefix(changed
, repoRelativePath
)) {
328 state
.setStagingState(StagingState
.MODIFIED
);
330 state
.setStagingState(StagingState
.NOT_STAGED
);
333 Set
<String
> conflicting
= indexDiffData
.getConflicting();
334 state
.setConflicts(containsPrefix(conflicting
, repoRelativePath
));
336 // locally modified / untracked
337 Set
<String
> modified
= indexDiffData
.getModified();
338 Set
<String
> untracked
= indexDiffData
.getUntracked();
339 Set
<String
> missing
= indexDiffData
.getMissing();
340 state
.setDirty(containsPrefix(modified
, repoRelativePath
)
341 || containsPrefix(untracked
, repoRelativePath
)
342 || containsPrefix(missing
, repoRelativePath
));
346 private boolean containsPrefix(Set
<String
> collection
, String prefix
) {
347 // when prefix is empty we are handling repository root, therefore we
348 // should return true whenever collection isn't empty
349 if (prefix
.length() == 1 && !collection
.isEmpty())
352 for (String path
: collection
)
353 if (path
.startsWith(prefix
))
358 private boolean containsPrefixPath(Set
<String
> collection
, String path
) {
359 for (String entry
: collection
) {
361 if (entry
.endsWith("/")) //$NON-NLS-1$
364 entryPath
= entry
+ "/"; //$NON-NLS-1$
365 if (path
.startsWith(entryPath
))
371 private interface FileSystemItem
{
372 boolean hasContainerAnyFiles();
374 boolean isContainer();
377 IPath
getAbsolutePath();
380 Repository
getRepository();
383 private static class FileItem
implements FileSystemItem
{
386 private final File file
;
388 public FileItem(@NonNull File file
) {
394 public IPath
getAbsolutePath() {
395 return new org
.eclipse
.core
.runtime
.Path(file
.getAbsolutePath());
399 public Repository
getRepository() {
400 return ResourceUtil
.getRepository(getAbsolutePath());
404 public boolean isContainer() {
405 return file
.isDirectory();
409 public boolean hasContainerAnyFiles() {
410 if (!isContainer()) {
411 throw new IllegalArgumentException("Container expected"); //$NON-NLS-1$
414 final boolean[] result
= new boolean[] { false };
415 final Path dotGit
= Paths
.get(Constants
.DOT_GIT
);
416 Files
.walkFileTree(file
.toPath(), new FileVisitor
<Path
>() {
418 public FileVisitResult
preVisitDirectory(Path dir
,
419 BasicFileAttributes attrs
) throws IOException
{
420 if (dotGit
.equals(dir
.getFileName())) {
421 return FileVisitResult
.SKIP_SUBTREE
;
423 return FileVisitResult
.CONTINUE
;
427 public FileVisitResult
visitFile(Path path
,
428 BasicFileAttributes attrs
) throws IOException
{
429 if (!attrs
.isDirectory()) {
431 return FileVisitResult
.TERMINATE
;
433 return FileVisitResult
.CONTINUE
;
437 public FileVisitResult
visitFileFailed(Path path
,
438 IOException exc
) throws IOException
{
439 return FileVisitResult
.CONTINUE
;
443 public FileVisitResult
postVisitDirectory(Path dir
,
444 IOException exc
) throws IOException
{
445 return FileVisitResult
.CONTINUE
;
449 } catch (IOException e
) {
450 // if can't get any info, treat as with file
456 private static class ResourceItem
implements FileSystemItem
{
459 private final IResource resource
;
461 public ResourceItem(@NonNull IResource resource
) {
462 this.resource
= resource
;
467 public IPath
getAbsolutePath() {
468 return resource
.getLocation();
472 public Repository
getRepository() {
473 return ResourceUtil
.getRepository(resource
);
477 public boolean isContainer() {
478 return isContainer(resource
);
482 public boolean hasContainerAnyFiles() {
483 return containsFiles(resource
);
486 private boolean isContainer(IResource rsc
) {
487 int type
= rsc
.getType();
488 return type
== IResource
.FOLDER
|| type
== IResource
.PROJECT
489 || type
== IResource
.ROOT
;
492 private boolean containsFiles(IResource rsc
) {
493 if (rsc
instanceof IContainer
) {
494 IContainer container
= (IContainer
) rsc
;
496 return anyFile(container
.members());
497 } catch (CoreException e
) {
498 // if can't get any info, treat as with file
502 throw new IllegalArgumentException(
503 "Expected a container resource."); //$NON-NLS-1$
506 private boolean anyFile(IResource
[] members
) {
507 for (IResource member
: members
) {
508 if (member
.getType() == IResource
.FILE
) {
510 } else if (isContainer(member
) && containsFiles(member
)) {