LabelDecoration should show ellipsis if HEAD is on a commit
[egit/spearce.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / decorators / DecoratableResourceAdapter.java
blobd480a730e8cb5296efdc74d1d828a7817bfa83b7
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;
17 import java.io.File;
18 import java.io.IOException;
19 import java.util.Collections;
20 import java.util.Set;
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)
87 throws IOException {
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();
96 if (gitDir != null)
97 repositoryName = repository.getDirectory().getParentFile()
98 .getName();
99 else
100 repositoryName = ""; //$NON-NLS-1$
101 branch = getShortBranch();
103 TreeWalk treeWalk = createThreeWayTreeWalk();
104 if (treeWalk == null)
105 return;
107 switch (resource.getType()) {
108 case IResource.FILE:
109 if (!treeWalk.next())
110 return;
111 extractResourceProperties(treeWalk);
112 break;
113 case IResource.PROJECT:
114 tracked = true;
115 case IResource.FOLDER:
116 extractContainerProperties(treeWalk);
117 break;
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)
136 return;
138 if (isIgnored(resourceEntry.getResource())) {
139 ignored = true;
140 return;
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())
148 return;
150 tracked = true;
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(
158 T_HEAD, T_INDEX))) {
159 staged = Staged.MODIFIED;
160 } else {
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)
170 return;
172 if (indexEntry.getStage() > 0)
173 conflicts = true;
175 if (indexEntry.isAssumeValid()) {
176 dirty = false;
177 assumeValid = true;
178 } else {
179 if (!timestampMatches(indexEntry, resourceEntry))
180 dirty = true;
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() {
197 recurseLimit = store
198 .getInt(UIPreferences.DECORATOR_RECURSIVE_LIMIT);
201 @Override
202 public boolean include(TreeWalk treeWalk)
203 throws MissingObjectException, IncorrectObjectTypeException,
204 IOException {
206 if (treeWalk.getFileMode(T_HEAD) == FileMode.MISSING
207 && treeWalk.getFileMode(T_INDEX) == FileMode.MISSING)
208 return false;
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);
219 filesChecked++;
221 // Merge results with old state
222 ignored = false;
223 assumeValid = false;
224 dirty = wasDirty || dirty;
225 conflicts = hadConflicts || conflicts;
226 if (staged != wasStaged && filesChecked > 1)
227 staged = Staged.MODIFIED;
229 return false;
232 private boolean shouldRecurse(TreeWalk treeWalk) {
233 final WorkingTreeIterator workspaceIterator = treeWalk.getTree(
234 T_WORKSPACE, WorkingTreeIterator.class);
236 if (workspaceIterator instanceof AdaptableFileTreeIterator)
237 return true;
239 ResourceEntry resourceEntry = null;
240 if (workspaceIterator != null)
241 resourceEntry = ((ContainerTreeIterator) workspaceIterator)
242 .getResourceEntry();
244 if (resourceEntry == null)
245 return true;
247 IResource visitingResource = resourceEntry.getResource();
248 if (targetDepth == -1) {
249 if (visitingResource.equals(resource)
250 || visitingResource.getParent().equals(resource))
251 targetDepth = treeWalk.getDepth();
252 else
253 return true;
256 if ((treeWalk.getDepth() - targetDepth) >= recurseLimit) {
257 if (visitingResource.equals(resource))
258 extractResourceProperties(treeWalk);
260 return false;
263 return true;
266 @Override
267 public TreeFilter clone() {
268 RecursiveStateFilter clone = new RecursiveStateFilter();
269 clone.filesChecked = this.filesChecked;
270 return clone;
273 @Override
274 public boolean shouldBeRecursive() {
275 return true;
279 private void extractContainerProperties(TreeWalk treeWalk) throws IOException {
281 if (isIgnored(resource)) {
282 ignored = true;
283 return;
286 treeWalk.setFilter(AndTreeFilter.create(treeWalk.getFilter(),
287 new RecursiveStateFilter()));
288 treeWalk.setRecursive(true);
290 treeWalk.next();
294 * Adds a filter to the specified tree walk limiting the results to only
295 * those matching the resource specified by <code>resourceToFilterBy</code>
296 * <p>
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.
301 * @param treeWalk
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())
314 return false;
316 if (repositoryPaths.contains("")) //$NON-NLS-1$
317 return true; // Project filter
319 treeWalk.setFilter(PathFilterGroup.createFromStrings(repositoryPaths));
320 return true;
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))
334 return null;
336 treeWalk.setRecursive(treeWalk.getFilter().shouldBeRecursive());
337 treeWalk.reset();
339 // Repository
340 if (headId != null)
341 treeWalk.addTree(new RevWalk(repository).parseTree(headId));
342 else
343 treeWalk.addTree(new EmptyTreeIterator());
345 // Index
346 treeWalk.addTree(new DirCacheIterator(DirCache.read(repository)));
348 // Working directory
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));
357 else
358 treeWalk.addTree(new AdaptableFileTreeIterator(repoRoot,
359 workspaceRoot));
361 return treeWalk;
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
373 // index.
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;
380 } else {
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() {
403 return branch;
406 public boolean isTracked() {
407 return tracked;
410 public boolean isIgnored() {
411 return ignored;
414 public boolean isDirty() {
415 return dirty;
418 public Staged staged() {
419 return staged;
422 public boolean hasConflicts() {
423 return conflicts;
426 public boolean isAssumeValid() {
427 return assumeValid;