Change GitResourceDecorator to use isAccessible
[egit.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitResourceDecorator.java
blob1055fcc9edc0f2c772a121bc4441b49e1638b056
1 /*******************************************************************************
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * Copyright (C) 2008, Google Inc.
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * See LICENSE for the full license text, also available.
10 *******************************************************************************/
11 package org.spearce.egit.ui.internal.decorators;
13 import java.io.IOException;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.LinkedHashSet;
17 import java.util.Set;
19 import org.eclipse.core.resources.IContainer;
20 import org.eclipse.core.resources.IFile;
21 import org.eclipse.core.resources.IProject;
22 import org.eclipse.core.resources.IResource;
23 import org.eclipse.core.resources.IResourceChangeEvent;
24 import org.eclipse.core.resources.IResourceChangeListener;
25 import org.eclipse.core.resources.IResourceDelta;
26 import org.eclipse.core.resources.IResourceDeltaVisitor;
27 import org.eclipse.core.resources.IResourceVisitor;
28 import org.eclipse.core.resources.ResourcesPlugin;
29 import org.eclipse.core.runtime.CoreException;
30 import org.eclipse.core.runtime.IAdaptable;
31 import org.eclipse.core.runtime.IProgressMonitor;
32 import org.eclipse.core.runtime.IStatus;
33 import org.eclipse.core.runtime.QualifiedName;
34 import org.eclipse.core.runtime.Status;
35 import org.eclipse.core.runtime.jobs.ISchedulingRule;
36 import org.eclipse.core.runtime.jobs.Job;
37 import org.eclipse.jface.viewers.IDecoration;
38 import org.eclipse.jface.viewers.ILightweightLabelDecorator;
39 import org.eclipse.jface.viewers.LabelProvider;
40 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
41 import org.eclipse.swt.widgets.Display;
42 import org.eclipse.team.core.Team;
43 import org.eclipse.ui.IDecoratorManager;
44 import org.spearce.egit.core.project.GitProjectData;
45 import org.spearce.egit.core.project.RepositoryChangeListener;
46 import org.spearce.egit.core.project.RepositoryMapping;
47 import org.spearce.egit.ui.Activator;
48 import org.spearce.egit.ui.UIIcons;
49 import org.spearce.egit.ui.UIText;
50 import org.spearce.jgit.lib.GitIndex;
51 import org.spearce.jgit.lib.IndexChangedEvent;
52 import org.spearce.jgit.lib.RefsChangedEvent;
53 import org.spearce.jgit.lib.Repository;
54 import org.spearce.jgit.lib.RepositoryChangedEvent;
55 import org.spearce.jgit.lib.RepositoryListener;
56 import org.spearce.jgit.lib.RepositoryState;
57 import org.spearce.jgit.lib.Tree;
58 import org.spearce.jgit.lib.TreeEntry;
59 import org.spearce.jgit.lib.GitIndex.Entry;
61 /**
62 * Supplies annotations for displayed resources.
63 * <p>
64 * This decorator provides annotations to indicate the status of each resource
65 * when compared to <code>HEAD</code> as well as the index in the relevant
66 * repository.
68 * When either the index or the working directory is different from HEAD an
69 * indicator is set.
71 * </p>
73 public class GitResourceDecorator extends LabelProvider implements
74 ILightweightLabelDecorator {
76 static final String decoratorId = "org.spearce.egit.ui.internal.decorators.GitResourceDecorator";
77 static class ResCL extends Job implements IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
79 ResCL() {
80 super("Git resource decorator trigger");
83 GitResourceDecorator getActiveDecorator() {
84 IDecoratorManager decoratorManager = Activator.getDefault()
85 .getWorkbench().getDecoratorManager();
86 if (decoratorManager.getEnabled(decoratorId))
87 return (GitResourceDecorator) decoratorManager
88 .getLightweightLabelDecorator(decoratorId);
89 return null;
92 private Set<IResource> resources = new LinkedHashSet<IResource>();
94 public void refsChanged(RefsChangedEvent e) {
95 repositoryChanged(e);
98 public void indexChanged(IndexChangedEvent e) {
99 repositoryChanged(e);
102 private void repositoryChanged(RepositoryChangedEvent e) {
103 Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
104 for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
105 RepositoryMapping mapping = RepositoryMapping.getMapping(p);
106 if (mapping != null && mapping.getRepository() == e.getRepository())
107 ms.add(mapping);
109 for (RepositoryMapping m : ms) {
110 repositoryChanged(m);
114 public void repositoryChanged(final RepositoryMapping which) {
115 synchronized (resources) {
116 resources.add(which.getContainer());
118 schedule();
121 @Override
122 protected IStatus run(IProgressMonitor arg0) {
123 try {
124 if (resources.size() > 0) {
125 IResource m;
126 synchronized(resources) {
127 Iterator<IResource> i = resources.iterator();
128 m = i.next();
129 i.remove();
131 while (!m.isAccessible()) {
132 if (!i.hasNext())
133 return Status.OK_STATUS;
134 m = i.next();
135 i.remove();
138 if (resources.size() > 0)
139 schedule();
141 ISchedulingRule markerRule = m.getWorkspace().getRuleFactory().markerRule(m);
142 getJobManager().beginRule(markerRule, arg0);
143 try {
144 m.accept(new IResourceVisitor() {
145 public boolean visit(IResource resource) throws CoreException {
146 GitResourceDecorator decorator = getActiveDecorator();
147 if (decorator != null)
148 decorator.clearDecorationState(resource);
149 return true;
152 IResource.DEPTH_INFINITE,
153 true);
154 } finally {
155 getJobManager().endRule(markerRule);
158 return Status.OK_STATUS;
159 } catch (Exception e) {
160 // We must be silent here or the UI will panic with lots of error messages
161 Activator.logError("Failed to trigger resource re-decoration", e);
162 return Status.OK_STATUS;
166 public void resourceChanged(IResourceChangeEvent event) {
167 if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
168 return;
170 try {
171 event.getDelta().accept(new IResourceDeltaVisitor() {
172 public boolean visit(IResourceDelta delta)
173 throws CoreException {
174 for (IResource r = delta.getResource(); r.getType() != IResource.ROOT; r = r
175 .getParent()) {
176 synchronized (resources) {
177 resources.add(r);
180 return true;
183 true
185 } catch (Exception e) {
186 Activator.logError("Problem during decorations. Stopped", e);
188 schedule();
191 void force() {
192 for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
193 synchronized (resources) {
194 resources.add(p);
197 schedule();
199 } // End ResCL
201 void clearDecorationState(IResource r) throws CoreException {
202 if (r.isAccessible()) {
203 r.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, null);
204 fireLabelProviderChanged(new LabelProviderChangedEvent(this, r));
208 static ResCL myrescl = new ResCL();
210 static {
211 Repository.addAnyRepositoryChangedListener(myrescl);
212 GitProjectData.addRepositoryChangeListener(myrescl);
213 ResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl,
214 IResourceChangeEvent.POST_CHANGE);
218 * Request that the decorator be updated, to reflect any recent changes.
219 * <p>
220 * Can be invoked any any thread. If the current thread is not the UI
221 * thread, an async update will be scheduled.
222 * </p>
224 public static void refresh() {
225 myrescl.force();
228 private static IResource toIResource(final Object e) {
229 if (e instanceof IResource)
230 return (IResource) e;
231 if (e instanceof IAdaptable) {
232 final Object c = ((IAdaptable) e).getAdapter(IResource.class);
233 if (c instanceof IResource)
234 return (IResource) c;
236 return null;
239 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName(
240 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
241 "dirty");
243 static final int UNCHANGED = 0;
245 static final int CHANGED = 1;
247 private Boolean isDirty(IResource rsrc) {
248 try {
249 if (rsrc.getType() == IResource.FILE && Team.isIgnored((IFile)rsrc))
250 return Boolean.FALSE;
252 RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
253 if (mapped != null) {
254 if (rsrc instanceof IContainer) {
255 for (IResource r : ((IContainer) rsrc)
256 .members(IContainer.EXCLUDE_DERIVED)) {
257 Boolean f = isDirty(r);
258 if (f == null || f.booleanValue())
259 return Boolean.TRUE;
261 return Boolean.FALSE;
264 return Boolean.valueOf(mapped.isResourceChanged(rsrc));
266 return null; // not mapped
267 } catch (CoreException e) {
268 // TODO Auto-generated catch block
269 e.printStackTrace();
270 } catch (IOException e) {
271 // TODO Auto-generated catch block
272 e.printStackTrace();
274 return null;
277 public void decorate(final Object element, final IDecoration decoration) {
278 final IResource rsrc = toIResource(element);
279 if (rsrc == null)
280 return;
282 // If the workspace has not been refreshed properly a resource might
283 // not actually exist, so we ignore these and do not decorate them
284 if (!rsrc.exists() && !rsrc.isPhantom()) {
285 Activator.trace("Tried to decorate non-existent resource "+rsrc);
286 return;
289 RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
291 // TODO: How do I see a renamed resource?
292 // TODO: Even trickier: when a path change from being blob to tree?
293 try {
294 if (mapped != null) {
295 Repository repository = mapped.getRepository();
296 GitIndex index = repository.getIndex();
297 String repoRelativePath = mapped.getRepoRelativePath(rsrc);
298 Tree headTree = repository.mapTree("HEAD");
299 TreeEntry blob = headTree!=null ? headTree.findBlobMember(repoRelativePath) : null;
300 Entry entry = index.getEntry(repoRelativePath);
301 if (entry == null) {
302 if (blob == null) {
303 if (rsrc instanceof IContainer) {
304 Integer df = (Integer) rsrc
305 .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
306 Boolean f = df == null ? isDirty(rsrc)
307 : Boolean.valueOf(df.intValue() == CHANGED);
308 if (f != null) {
309 if (f.booleanValue()) {
310 decoration.addPrefix(">"); // Have not
311 // seen
312 orState(rsrc, CHANGED);
313 } else {
314 orState(rsrc, UNCHANGED);
315 // decoration.addSuffix("=?");
317 } else {
318 decoration.addSuffix(" ?* ");
321 if (rsrc instanceof IProject) {
322 Repository repo = mapped.getRepository();
323 try {
324 String branch = repo.getBranch();
325 if (repo.isStGitMode()) {
326 String patch = repo.getPatch();
327 decoration.addSuffix(" [StGit " + patch + "@" + branch
328 + "]");
329 } else {
330 RepositoryState repositoryState = repo.getRepositoryState();
331 String statename;
332 if (repositoryState.equals(RepositoryState.SAFE))
333 statename = "";
334 else
335 statename = repositoryState.getDescription() + " ";
336 decoration.addSuffix(" [Git " + statename + "@ " + branch + "]");
338 } catch (IOException e) {
339 e.printStackTrace();
340 decoration.addSuffix(" [Git ?]");
342 decoration.addOverlay(UIIcons.OVR_SHARED);
345 } else {
346 if (Team.isIgnoredHint(rsrc)) {
347 decoration.addSuffix("(ignored)");
348 } else {
349 decoration.addPrefix(">");
350 decoration.addSuffix("(untracked)");
351 orState(rsrc.getParent(), CHANGED);
354 } else {
355 if (!(rsrc instanceof IContainer)) {
356 decoration.addSuffix("(deprecated)"); // Will drop on
357 // commit
358 decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE);
359 orState(rsrc.getParent(), CHANGED);
362 } else {
363 if (entry.getStage() != GitIndex.STAGE_0) {
364 decoration.addSuffix("(conflict)");
365 decoration.addOverlay(UIIcons.OVR_CONFLICT);
366 orState(rsrc.getParent(), CHANGED);
367 return;
370 if (blob == null) {
371 decoration.addOverlay(UIIcons.OVR_PENDING_ADD);
372 orState(rsrc.getParent(), CHANGED);
373 } else {
375 if (entry.isAssumedValid()) {
376 decoration.addOverlay(UIIcons.OVR_ASSUMEVALID);
377 return;
380 decoration.addOverlay(UIIcons.OVR_SHARED);
382 if (entry.isModified(mapped.getWorkDir(), true)) {
383 decoration.addPrefix(">");
384 decoration.addSuffix("(not updated)");
385 orState(rsrc.getParent(), CHANGED);
386 } else {
387 if (!entry.getObjectId().equals(blob.getId()))
388 decoration.addPrefix(">");
389 else
390 decoration.addPrefix(""); // set it to avoid further calls
395 } catch (IOException e) {
396 decoration.addSuffix("?");
397 // If we throw an exception Eclipse will log the error and
398 // unregister us thereby preventing us from dragging down the
399 // entire workbench because we are crashing.
401 throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
402 } catch (CoreException e) {
403 throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
407 private void orState(final IResource rsrc, int flag) {
408 if (rsrc == null || rsrc.getType() == IResource.ROOT) {
409 return;
412 try {
413 Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
414 Runnable runnable = new Runnable() {
415 public void run() {
416 // Async could be called after a
417 // project is closed or a
418 // resource is deleted
419 if (!rsrc.isAccessible())
420 return;
421 fireLabelProviderChanged(new LabelProviderChangedEvent(
422 GitResourceDecorator.this, rsrc));
425 if (dirty == null) {
426 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, new Integer(flag));
427 orState(rsrc.getParent(), flag);
428 // if (Thread.currentThread() == Display.getDefault().getThread())
429 // runnable.run();
430 // else
431 Display.getDefault().asyncExec(runnable);
432 } else {
433 if ((dirty.intValue() | flag) != dirty.intValue()) {
434 dirty = new Integer(dirty.intValue() | flag);
435 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, dirty);
436 orState(rsrc.getParent(), dirty.intValue());
437 // if (Thread.currentThread() == Display.getDefault().getThread())
438 // runnable.run();
439 // else
440 Display.getDefault().asyncExec(runnable);
443 } catch (CoreException e) {
444 // TODO Auto-generated catch block
445 e.printStackTrace();
449 @Override
450 public boolean isLabelProperty(Object element, String property) {
451 return super.isLabelProperty(element, property);