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>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * which accompanies this distribution, and is available at
12 * http://www.eclipse.org/legal/epl-v10.html
13 *******************************************************************************/
15 package org
.eclipse
.egit
.ui
.internal
.decorators
;
18 import java
.io
.IOException
;
19 import java
.util
.Collections
;
22 import org
.eclipse
.core
.resources
.IProject
;
23 import org
.eclipse
.core
.resources
.IResource
;
24 import org
.eclipse
.core
.resources
.IWorkspaceRoot
;
25 import org
.eclipse
.egit
.core
.AdaptableFileTreeIterator
;
26 import org
.eclipse
.egit
.core
.ContainerTreeIterator
;
27 import org
.eclipse
.egit
.core
.ContainerTreeIterator
.ResourceEntry
;
28 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
29 import org
.eclipse
.egit
.ui
.Activator
;
30 import org
.eclipse
.egit
.ui
.UIPreferences
;
31 import org
.eclipse
.jface
.preference
.IPreferenceStore
;
32 import org
.eclipse
.jgit
.dircache
.DirCache
;
33 import org
.eclipse
.jgit
.dircache
.DirCacheEntry
;
34 import org
.eclipse
.jgit
.dircache
.DirCacheIterator
;
35 import org
.eclipse
.jgit
.errors
.IncorrectObjectTypeException
;
36 import org
.eclipse
.jgit
.errors
.MissingObjectException
;
37 import org
.eclipse
.jgit
.lib
.Constants
;
38 import org
.eclipse
.jgit
.lib
.FileMode
;
39 import org
.eclipse
.jgit
.lib
.ObjectId
;
40 import org
.eclipse
.jgit
.lib
.Ref
;
41 import org
.eclipse
.jgit
.lib
.Repository
;
42 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
43 import org
.eclipse
.jgit
.treewalk
.EmptyTreeIterator
;
44 import org
.eclipse
.jgit
.treewalk
.TreeWalk
;
45 import org
.eclipse
.jgit
.treewalk
.WorkingTreeIterator
;
46 import org
.eclipse
.jgit
.treewalk
.filter
.AndTreeFilter
;
47 import org
.eclipse
.jgit
.treewalk
.filter
.PathFilterGroup
;
48 import org
.eclipse
.jgit
.treewalk
.filter
.TreeFilter
;
49 import org
.eclipse
.team
.core
.Team
;
51 class DecoratableResourceAdapter
implements IDecoratableResource
{
53 private final IResource resource
;
55 private final RepositoryMapping mapping
;
57 private final Repository repository
;
59 private final ObjectId headId
;
61 private final IPreferenceStore store
;
63 private final String branch
;
65 private final String repositoryName
;
67 private boolean tracked
= false;
69 private boolean ignored
= false;
71 private boolean dirty
= false;
73 private boolean conflicts
= false;
75 private boolean assumeValid
= false;
77 private Staged staged
= Staged
.NOT_STAGED
;
79 static final int T_HEAD
= 0;
81 static final int T_INDEX
= 1;
83 static final int T_WORKSPACE
= 2;
85 @SuppressWarnings("fallthrough")
86 public DecoratableResourceAdapter(IResource resourceToWrap
)
88 resource
= resourceToWrap
;
89 mapping
= RepositoryMapping
.getMapping(resource
);
90 repository
= mapping
.getRepository();
91 headId
= repository
.resolve(Constants
.HEAD
);
93 store
= Activator
.getDefault().getPreferenceStore();
95 File gitDir
= repository
.getDirectory();
97 repositoryName
= repository
.getDirectory().getParentFile()
100 repositoryName
= ""; //$NON-NLS-1$
101 branch
= getShortBranch();
103 TreeWalk treeWalk
= createThreeWayTreeWalk();
104 if (treeWalk
== null)
107 switch (resource
.getType()) {
109 if (!treeWalk
.next())
111 extractResourceProperties(treeWalk
);
113 case IResource
.PROJECT
:
115 case IResource
.FOLDER
:
116 extractContainerProperties(treeWalk
);
121 private String
getShortBranch() throws IOException
{
122 Ref head
= repository
.getRef(Constants
.HEAD
);
123 if (head
!= null && !head
.isSymbolic())
124 return repository
.getFullBranch().substring(0, 7) + "..."; //$NON-NLS-1$
126 return repository
.getBranch();
129 private void extractResourceProperties(TreeWalk treeWalk
) {
130 final ContainerTreeIterator workspaceIterator
= treeWalk
.getTree(
131 T_WORKSPACE
, ContainerTreeIterator
.class);
132 final ResourceEntry resourceEntry
= workspaceIterator
!= null ? workspaceIterator
133 .getResourceEntry() : null;
135 if (resourceEntry
== null)
138 if (isIgnored(resourceEntry
.getResource())) {
143 final int mHead
= treeWalk
.getRawMode(T_HEAD
);
144 final int mIndex
= treeWalk
.getRawMode(T_INDEX
);
146 if (mHead
== FileMode
.MISSING
.getBits()
147 && mIndex
== FileMode
.MISSING
.getBits())
152 if (mHead
== FileMode
.MISSING
.getBits()) {
153 staged
= Staged
.ADDED
;
154 } else if (mIndex
== FileMode
.MISSING
.getBits()) {
155 staged
= Staged
.REMOVED
;
156 } else if (mHead
!= mIndex
157 || (mIndex
!= FileMode
.TREE
.getBits() && !treeWalk
.idEqual(
159 staged
= Staged
.MODIFIED
;
161 staged
= Staged
.NOT_STAGED
;
164 final DirCacheIterator indexIterator
= treeWalk
.getTree(T_INDEX
,
165 DirCacheIterator
.class);
166 final DirCacheEntry indexEntry
= indexIterator
!= null ? indexIterator
167 .getDirCacheEntry() : null;
169 if (indexEntry
== null)
172 if (indexEntry
.getStage() > 0)
175 if (indexEntry
.isAssumeValid()) {
179 if (!timestampMatches(indexEntry
, resourceEntry
))
182 // TODO: Consider doing a content check here, to rule out false
183 // positives, as we might get mismatch between timestamps, even
184 // if the content is the same.
188 private class RecursiveStateFilter
extends TreeFilter
{
190 private int filesChecked
= 0;
192 private int targetDepth
= -1;
194 private final int recurseLimit
;
196 public RecursiveStateFilter() {
198 .getInt(UIPreferences
.DECORATOR_RECURSIVE_LIMIT
);
202 public boolean include(TreeWalk treeWalk
)
203 throws MissingObjectException
, IncorrectObjectTypeException
,
206 if (treeWalk
.getFileMode(T_HEAD
) == FileMode
.MISSING
207 && treeWalk
.getFileMode(T_INDEX
) == FileMode
.MISSING
)
210 if (FileMode
.TREE
.equals(treeWalk
.getRawMode(T_WORKSPACE
)))
211 return shouldRecurse(treeWalk
);
213 // Backup current state so far
214 Staged wasStaged
= staged
;
215 boolean wasDirty
= dirty
;
216 boolean hadConflicts
= conflicts
;
218 extractResourceProperties(treeWalk
);
221 // Merge results with old state
224 dirty
= wasDirty
|| dirty
;
225 conflicts
= hadConflicts
|| conflicts
;
226 if (staged
!= wasStaged
&& filesChecked
> 1)
227 staged
= Staged
.MODIFIED
;
232 private boolean shouldRecurse(TreeWalk treeWalk
) {
233 final WorkingTreeIterator workspaceIterator
= treeWalk
.getTree(
234 T_WORKSPACE
, WorkingTreeIterator
.class);
236 if (workspaceIterator
instanceof AdaptableFileTreeIterator
)
239 ResourceEntry resourceEntry
= null;
240 if (workspaceIterator
!= null)
241 resourceEntry
= ((ContainerTreeIterator
) workspaceIterator
)
244 if (resourceEntry
== null)
247 IResource visitingResource
= resourceEntry
.getResource();
248 if (targetDepth
== -1) {
249 if (visitingResource
.equals(resource
)
250 || visitingResource
.getParent().equals(resource
))
251 targetDepth
= treeWalk
.getDepth();
256 if ((treeWalk
.getDepth() - targetDepth
) >= recurseLimit
) {
257 if (visitingResource
.equals(resource
))
258 extractResourceProperties(treeWalk
);
267 public TreeFilter
clone() {
268 RecursiveStateFilter clone
= new RecursiveStateFilter();
269 clone
.filesChecked
= this.filesChecked
;
274 public boolean shouldBeRecursive() {
279 private void extractContainerProperties(TreeWalk treeWalk
) throws IOException
{
281 if (isIgnored(resource
)) {
286 treeWalk
.setFilter(AndTreeFilter
.create(treeWalk
.getFilter(),
287 new RecursiveStateFilter()));
288 treeWalk
.setRecursive(true);
294 * Adds a filter to the specified tree walk limiting the results to only
295 * those matching the resource specified by <code>resourceToFilterBy</code>
297 * If the resource does not exists in the current repository, no filter is
298 * added and the method returns <code>false</code>. If the resource is a
299 * project, no filter is added, but the operation is considered a success.
302 * the tree walk to add the filter to
303 * @param resourceToFilterBy
304 * the resource to filter by
306 * @return <code>true</code> if the filter could be added,
307 * <code>false</code> otherwise
309 private boolean addResourceFilter(final TreeWalk treeWalk
,
310 final IResource resourceToFilterBy
) {
311 Set
<String
> repositoryPaths
= Collections
.singleton(mapping
312 .getRepoRelativePath(resourceToFilterBy
));
313 if (repositoryPaths
.isEmpty())
316 if (repositoryPaths
.contains("")) //$NON-NLS-1$
317 return true; // Project filter
319 treeWalk
.setFilter(PathFilterGroup
.createFromStrings(repositoryPaths
));
324 * Helper method to create a new tree walk between the repository, the
325 * index, and the working tree.
327 * @return the created tree walk, or null if it could not be created
328 * @throws IOException
329 * if there were errors when creating the tree walk
331 private TreeWalk
createThreeWayTreeWalk() throws IOException
{
332 final TreeWalk treeWalk
= new TreeWalk(repository
);
333 if (!addResourceFilter(treeWalk
, resource
))
336 treeWalk
.setRecursive(treeWalk
.getFilter().shouldBeRecursive());
341 treeWalk
.addTree(new RevWalk(repository
).parseTree(headId
));
343 treeWalk
.addTree(new EmptyTreeIterator());
346 treeWalk
.addTree(new DirCacheIterator(DirCache
.read(repository
)));
349 IProject project
= resource
.getProject();
350 IWorkspaceRoot workspaceRoot
= resource
.getWorkspace().getRoot();
351 File repoRoot
= repository
.getWorkDir();
353 if (repoRoot
.equals(project
.getLocation().toFile()))
354 treeWalk
.addTree(new ContainerTreeIterator(project
));
355 else if (repoRoot
.equals(workspaceRoot
.getLocation().toFile()))
356 treeWalk
.addTree(new ContainerTreeIterator(workspaceRoot
));
358 treeWalk
.addTree(new AdaptableFileTreeIterator(repoRoot
,
364 private static boolean timestampMatches(DirCacheEntry indexEntry
,
365 ResourceEntry resourceEntry
) {
366 long tIndex
= indexEntry
.getLastModified();
367 long tWorkspaceResource
= resourceEntry
.getLastModified();
370 // C-Git under Windows stores timestamps with 1-seconds resolution,
371 // so we need to check to see if this is the case here, and possibly
372 // fix the timestamp of the resource to match the resolution of the
374 // It also appears the timestamp in Java on Linux may also be rounded
375 // in which case the index timestamp may have subseconds, but not
376 // the timestamp from the workspace resource.
377 // If either timestamp looks rounded we skip the subscond part.
378 if (tIndex
% 1000 == 0 || tWorkspaceResource
% 1000 == 0) {
379 return tIndex
/ 1000 == tWorkspaceResource
/ 1000;
381 return tIndex
== tWorkspaceResource
;
385 private static boolean isIgnored(IResource resource
) {
386 // TODO: Also read ignores from .git/info/excludes et al.
387 return Team
.isIgnoredHint(resource
);
390 public String
getName() {
391 return resource
.getName();
394 public int getType() {
395 return resource
.getType();
398 public String
getRepositoryName() {
399 return repositoryName
;
402 public String
getBranch() {
406 public boolean isTracked() {
410 public boolean isIgnored() {
414 public boolean isDirty() {
418 public Staged
staged() {
422 public boolean hasConflicts() {
426 public boolean isAssumeValid() {