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
;
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
;
61 * Supplies annotations for displayed resources.
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
67 * When either the index or the working directory is different from HEAD an
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
{
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
);
91 private Set
<IResource
> resources
= new LinkedHashSet
<IResource
>();
93 public void refsChanged(RefsChangedEvent e
) {
97 public void indexChanged(IndexChangedEvent 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())
108 for (RepositoryMapping m
: ms
) {
109 repositoryChanged(m
);
113 public void repositoryChanged(final RepositoryMapping which
) {
114 synchronized (resources
) {
115 resources
.add(which
.getContainer());
121 protected IStatus
run(IProgressMonitor arg0
) {
123 if (resources
.size() > 0) {
125 synchronized(resources
) {
126 Iterator
<IResource
> i
= resources
.iterator();
129 if (resources
.size() > 0)
132 ISchedulingRule markerRule
= m
.getWorkspace().getRuleFactory().markerRule(m
);
133 getJobManager().beginRule(markerRule
, arg0
);
135 m
.accept(new IResourceVisitor() {
136 public boolean visit(IResource resource
) throws CoreException
{
137 getActiveDecorator().clearDecorationState(resource
);
141 IResource
.DEPTH_INFINITE
,
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
) {
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
165 synchronized (resources
) {
174 } catch (Exception e
) {
175 Activator
.logError("Problem during decorations. Stopped", e
);
181 for (IProject p
: ResourcesPlugin
.getWorkspace().getRoot().getProjects()) {
182 synchronized (resources
) {
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();
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.
208 * Can be invoked any any thread. If the current thread is not the UI
209 * thread, an async update will be scheduled.
212 public static void refresh() {
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
;
227 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY
= new QualifiedName(
228 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
231 static final int UNCHANGED
= 0;
233 static final int CHANGED
= 1;
235 private Boolean
isDirty(IResource rsrc
) {
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())
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
258 } catch (IOException e
) {
259 // TODO Auto-generated catch block
265 public void decorate(final Object element
, final IDecoration decoration
) {
266 final IResource rsrc
= toIResource(element
);
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
);
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?
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
);
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
);
297 if (f
.booleanValue()) {
298 decoration
.addPrefix(">"); // Have not
300 orState(rsrc
, CHANGED
);
302 orState(rsrc
, UNCHANGED
);
303 // decoration.addSuffix("=?");
306 decoration
.addSuffix(" ?* ");
309 if (rsrc
instanceof IProject
) {
310 Repository repo
= mapped
.getRepository();
312 String branch
= repo
.getBranch();
313 if (repo
.isStGitMode()) {
314 String patch
= repo
.getPatch();
315 decoration
.addSuffix(" [StGit " + patch
+ "@" + branch
318 RepositoryState repositoryState
= repo
.getRepositoryState();
320 if (repositoryState
.equals(RepositoryState
.SAFE
))
323 statename
= repositoryState
.getDescription() + " ";
324 decoration
.addSuffix(" [Git " + statename
+ "@ " + branch
+ "]");
326 } catch (IOException e
) {
328 decoration
.addSuffix(" [Git ?]");
330 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
334 if (Team
.isIgnoredHint(rsrc
)) {
335 decoration
.addSuffix("(ignored)");
337 decoration
.addPrefix(">");
338 decoration
.addSuffix("(untracked)");
339 orState(rsrc
.getParent(), CHANGED
);
343 if (!(rsrc
instanceof IContainer
)) {
344 decoration
.addSuffix("(deprecated)"); // Will drop on
346 decoration
.addOverlay(UIIcons
.OVR_PENDING_REMOVE
);
347 orState(rsrc
.getParent(), CHANGED
);
351 if (entry
.getStage() != GitIndex
.STAGE_0
) {
352 decoration
.addSuffix("(conflict)");
353 decoration
.addOverlay(UIIcons
.OVR_CONFLICT
);
354 orState(rsrc
.getParent(), CHANGED
);
359 decoration
.addOverlay(UIIcons
.OVR_PENDING_ADD
);
360 orState(rsrc
.getParent(), CHANGED
);
363 if (entry
.isAssumedValid()) {
364 decoration
.addOverlay(UIIcons
.OVR_ASSUMEVALID
);
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
);
375 if (!entry
.getObjectId().equals(blob
.getId()))
376 decoration
.addPrefix(">");
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
) {
401 Integer dirty
= (Integer
) rsrc
.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
402 Runnable runnable
= new Runnable() {
404 // Async could be called after a
405 // project is closed or a
406 // resource is deleted
407 if (!rsrc
.isAccessible())
409 fireLabelProviderChanged(new LabelProviderChangedEvent(
410 GitResourceDecorator
.this, rsrc
));
414 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, new Integer(flag
));
415 orState(rsrc
.getParent(), flag
);
416 // if (Thread.currentThread() == Display.getDefault().getThread())
419 Display
.getDefault().asyncExec(runnable
);
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())
428 Display
.getDefault().asyncExec(runnable
);
431 } catch (CoreException e
) {
432 // TODO Auto-generated catch block
438 public boolean isLabelProperty(Object element
, String property
) {
439 return super.isLabelProperty(element
, property
);