Log decoration problems more silently
[egit/zawir.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitResourceDecorator.java
blob6d2f88ec13a0b4573513c5fec836e85d11bb833f
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>
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * See LICENSE for the full license text, also available.
9 *******************************************************************************/
10 package org.spearce.egit.ui.internal.decorators;
12 import java.io.IOException;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.LinkedHashSet;
16 import java.util.Set;
18 import org.eclipse.core.resources.IContainer;
19 import org.eclipse.core.resources.IFile;
20 import org.eclipse.core.resources.IProject;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.core.resources.IResourceChangeEvent;
23 import org.eclipse.core.resources.IResourceChangeListener;
24 import org.eclipse.core.resources.IResourceDelta;
25 import org.eclipse.core.resources.IResourceDeltaVisitor;
26 import org.eclipse.core.resources.IResourceVisitor;
27 import org.eclipse.core.resources.ResourcesPlugin;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.IAdaptable;
30 import org.eclipse.core.runtime.IProgressMonitor;
31 import org.eclipse.core.runtime.IStatus;
32 import org.eclipse.core.runtime.QualifiedName;
33 import org.eclipse.core.runtime.Status;
34 import org.eclipse.core.runtime.jobs.ISchedulingRule;
35 import org.eclipse.core.runtime.jobs.Job;
36 import org.eclipse.jface.viewers.IDecoration;
37 import org.eclipse.jface.viewers.ILightweightLabelDecorator;
38 import org.eclipse.jface.viewers.LabelProvider;
39 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.team.core.Team;
42 import org.eclipse.ui.IDecoratorManager;
43 import org.spearce.egit.core.project.GitProjectData;
44 import org.spearce.egit.core.project.RepositoryChangeListener;
45 import org.spearce.egit.core.project.RepositoryMapping;
46 import org.spearce.egit.ui.Activator;
47 import org.spearce.egit.ui.UIIcons;
48 import org.spearce.egit.ui.UIText;
49 import org.spearce.jgit.lib.GitIndex;
50 import org.spearce.jgit.lib.IndexChangedEvent;
51 import org.spearce.jgit.lib.RefsChangedEvent;
52 import org.spearce.jgit.lib.Repository;
53 import org.spearce.jgit.lib.RepositoryChangedEvent;
54 import org.spearce.jgit.lib.RepositoryListener;
55 import org.spearce.jgit.lib.RepositoryState;
56 import org.spearce.jgit.lib.Tree;
57 import org.spearce.jgit.lib.TreeEntry;
58 import org.spearce.jgit.lib.GitIndex.Entry;
60 /**
61 * Supplies annotations for displayed resources.
62 * <p>
63 * This decorator provides annotations to indicate the status of each resource
64 * when compared to <code>HEAD</code> as well as the index in the relevant
65 * repository.
67 * When either the index or the working directory is different from HEAD an
68 * indicator is set.
70 * </p>
72 public class GitResourceDecorator extends LabelProvider implements
73 ILightweightLabelDecorator {
75 static final String decoratorId = "org.spearce.egit.ui.internal.decorators.GitResourceDecorator";
76 static class ResCL extends Job implements IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
78 ResCL() {
79 super("Git resource decorator trigger");
82 GitResourceDecorator getActiveDecorator() {
83 IDecoratorManager decoratorManager = Activator.getDefault()
84 .getWorkbench().getDecoratorManager();
85 if (decoratorManager.getEnabled(decoratorId))
86 return (GitResourceDecorator) decoratorManager
87 .getLightweightLabelDecorator(decoratorId);
88 return null;
91 private Set<IResource> resources = new LinkedHashSet<IResource>();
93 public void refsChanged(RefsChangedEvent e) {
94 repositoryChanged(e);
97 public void indexChanged(IndexChangedEvent e) {
98 repositoryChanged(e);
101 private void repositoryChanged(RepositoryChangedEvent e) {
102 Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
103 for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
104 RepositoryMapping mapping = RepositoryMapping.getMapping(p);
105 if (mapping != null && mapping.getRepository() == e.getRepository())
106 ms.add(mapping);
108 for (RepositoryMapping m : ms) {
109 repositoryChanged(m);
113 public void repositoryChanged(final RepositoryMapping which) {
114 synchronized (resources) {
115 resources.add(which.getContainer());
117 schedule();
120 @Override
121 protected IStatus run(IProgressMonitor arg0) {
122 try {
123 if (resources.size() > 0) {
124 IResource m;
125 synchronized(resources) {
126 Iterator<IResource> i = resources.iterator();
127 m = i.next();
128 i.remove();
129 if (resources.size() > 0)
130 schedule();
132 ISchedulingRule markerRule = m.getWorkspace().getRuleFactory().markerRule(m);
133 getJobManager().beginRule(markerRule, arg0);
134 try {
135 m.accept(new IResourceVisitor() {
136 public boolean visit(IResource resource) throws CoreException {
137 getActiveDecorator().clearDecorationState(resource);
138 return true;
141 IResource.DEPTH_INFINITE,
142 true);
143 } finally {
144 getJobManager().endRule(markerRule);
147 return Status.OK_STATUS;
148 } catch (Exception e) {
149 // We must be silent here or the UI will panic with lots of error messages
150 Activator.logError("Failed to trigger resource re-decoration", e);
151 return Status.OK_STATUS;
155 public void resourceChanged(IResourceChangeEvent event) {
156 if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
157 return;
159 try {
160 event.getDelta().accept(new IResourceDeltaVisitor() {
161 public boolean visit(IResourceDelta delta)
162 throws CoreException {
163 for (IResource r = delta.getResource(); r.getType() != IResource.ROOT; r = r
164 .getParent()) {
165 synchronized (resources) {
166 resources.add(r);
169 return true;
172 true
174 } catch (Exception e) {
175 Activator.logError("Problem during decorations. Stopped", e);
177 schedule();
180 void force() {
181 for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
182 synchronized (resources) {
183 resources.add(p);
186 schedule();
188 } // End ResCL
190 void clearDecorationState(IResource r) throws CoreException {
191 if (r.isAccessible())
192 r.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, null);
193 fireLabelProviderChanged(new LabelProviderChangedEvent(this, r));
196 static ResCL myrescl = new ResCL();
198 static {
199 Repository.addAnyRepositoryChangedListener(myrescl);
200 GitProjectData.addRepositoryChangeListener(myrescl);
201 ResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl,
202 IResourceChangeEvent.POST_CHANGE);
206 * Request that the decorator be updated, to reflect any recent changes.
207 * <p>
208 * Can be invoked any any thread. If the current thread is not the UI
209 * thread, an async update will be scheduled.
210 * </p>
212 public static void refresh() {
213 myrescl.force();
216 private static IResource toIResource(final Object e) {
217 if (e instanceof IResource)
218 return (IResource) e;
219 if (e instanceof IAdaptable) {
220 final Object c = ((IAdaptable) e).getAdapter(IResource.class);
221 if (c instanceof IResource)
222 return (IResource) c;
224 return null;
227 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName(
228 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
229 "dirty");
231 static final int UNCHANGED = 0;
233 static final int CHANGED = 1;
235 private Boolean isDirty(IResource rsrc) {
236 try {
237 if (rsrc.getType() == IResource.FILE && Team.isIgnored((IFile)rsrc))
238 return Boolean.FALSE;
240 RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
241 if (mapped != null) {
242 if (rsrc instanceof IContainer) {
243 for (IResource r : ((IContainer) rsrc)
244 .members(IContainer.EXCLUDE_DERIVED)) {
245 Boolean f = isDirty(r);
246 if (f == null || f.booleanValue())
247 return Boolean.TRUE;
249 return Boolean.FALSE;
252 return Boolean.valueOf(mapped.isResourceChanged(rsrc));
254 return null; // not mapped
255 } catch (CoreException e) {
256 // TODO Auto-generated catch block
257 e.printStackTrace();
258 } catch (IOException e) {
259 // TODO Auto-generated catch block
260 e.printStackTrace();
262 return null;
265 public void decorate(final Object element, final IDecoration decoration) {
266 final IResource rsrc = toIResource(element);
267 if (rsrc == null)
268 return;
270 // If the workspace has not been refreshed properly a resource might
271 // not actually exist, so we ignore these and do not decorate them
272 if (!rsrc.exists() && !rsrc.isPhantom()) {
273 Activator.trace("Tried to decorate non-existent resource "+rsrc);
274 return;
277 RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
279 // TODO: How do I see a renamed resource?
280 // TODO: Even trickier: when a path change from being blob to tree?
281 try {
282 if (mapped != null) {
283 Repository repository = mapped.getRepository();
284 GitIndex index = repository.getIndex();
285 String repoRelativePath = mapped.getRepoRelativePath(rsrc);
286 Tree headTree = repository.mapTree("HEAD");
287 TreeEntry blob = headTree!=null ? headTree.findBlobMember(repoRelativePath) : null;
288 Entry entry = index.getEntry(repoRelativePath);
289 if (entry == null) {
290 if (blob == null) {
291 if (rsrc instanceof IContainer) {
292 Integer df = (Integer) rsrc
293 .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
294 Boolean f = df == null ? isDirty(rsrc)
295 : Boolean.valueOf(df.intValue() == CHANGED);
296 if (f != null) {
297 if (f.booleanValue()) {
298 decoration.addPrefix(">"); // Have not
299 // seen
300 orState(rsrc, CHANGED);
301 } else {
302 orState(rsrc, UNCHANGED);
303 // decoration.addSuffix("=?");
305 } else {
306 decoration.addSuffix(" ?* ");
309 if (rsrc instanceof IProject) {
310 Repository repo = mapped.getRepository();
311 try {
312 String branch = repo.getBranch();
313 if (repo.isStGitMode()) {
314 String patch = repo.getPatch();
315 decoration.addSuffix(" [StGit " + patch + "@" + branch
316 + "]");
317 } else {
318 RepositoryState repositoryState = repo.getRepositoryState();
319 String statename;
320 if (repositoryState.equals(RepositoryState.SAFE))
321 statename = "";
322 else
323 statename = repositoryState.getDescription() + " ";
324 decoration.addSuffix(" [Git " + statename + "@ " + branch + "]");
326 } catch (IOException e) {
327 e.printStackTrace();
328 decoration.addSuffix(" [Git ?]");
330 decoration.addOverlay(UIIcons.OVR_SHARED);
333 } else {
334 if (Team.isIgnoredHint(rsrc)) {
335 decoration.addSuffix("(ignored)");
336 } else {
337 decoration.addPrefix(">");
338 decoration.addSuffix("(untracked)");
339 orState(rsrc.getParent(), CHANGED);
342 } else {
343 if (!(rsrc instanceof IContainer)) {
344 decoration.addSuffix("(deprecated)"); // Will drop on
345 // commit
346 decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE);
347 orState(rsrc.getParent(), CHANGED);
350 } else {
351 if (entry.getStage() != GitIndex.STAGE_0) {
352 decoration.addSuffix("(conflict)");
353 decoration.addOverlay(UIIcons.OVR_CONFLICT);
354 orState(rsrc.getParent(), CHANGED);
355 return;
358 if (blob == null) {
359 decoration.addOverlay(UIIcons.OVR_PENDING_ADD);
360 orState(rsrc.getParent(), CHANGED);
361 } else {
363 if (entry.isAssumedValid()) {
364 decoration.addOverlay(UIIcons.OVR_ASSUMEVALID);
365 return;
368 decoration.addOverlay(UIIcons.OVR_SHARED);
370 if (entry.isModified(mapped.getWorkDir(), true)) {
371 decoration.addPrefix(">");
372 decoration.addSuffix("(not updated)");
373 orState(rsrc.getParent(), CHANGED);
374 } else {
375 if (!entry.getObjectId().equals(blob.getId()))
376 decoration.addPrefix(">");
377 else
378 decoration.addPrefix(""); // set it to avoid further calls
383 } catch (IOException e) {
384 decoration.addSuffix("?");
385 // If we throw an exception Eclipse will log the error and
386 // unregister us thereby preventing us from dragging down the
387 // entire workbench because we are crashing.
389 throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
390 } catch (CoreException e) {
391 throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
395 private void orState(final IResource rsrc, int flag) {
396 if (rsrc == null || rsrc.getType() == IResource.ROOT) {
397 return;
400 try {
401 Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
402 Runnable runnable = new Runnable() {
403 public void run() {
404 // Async could be called after a
405 // project is closed or a
406 // resource is deleted
407 if (!rsrc.isAccessible())
408 return;
409 fireLabelProviderChanged(new LabelProviderChangedEvent(
410 GitResourceDecorator.this, rsrc));
413 if (dirty == null) {
414 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, new Integer(flag));
415 orState(rsrc.getParent(), flag);
416 // if (Thread.currentThread() == Display.getDefault().getThread())
417 // runnable.run();
418 // else
419 Display.getDefault().asyncExec(runnable);
420 } else {
421 if ((dirty.intValue() | flag) != dirty.intValue()) {
422 dirty = new Integer(dirty.intValue() | flag);
423 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, dirty);
424 orState(rsrc.getParent(), dirty.intValue());
425 // if (Thread.currentThread() == Display.getDefault().getThread())
426 // runnable.run();
427 // else
428 Display.getDefault().asyncExec(runnable);
431 } catch (CoreException e) {
432 // TODO Auto-generated catch block
433 e.printStackTrace();
437 @Override
438 public boolean isLabelProperty(Object element, String property) {
439 return super.isLabelProperty(element, property);