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
;
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
;
62 * Supplies annotations for displayed resources.
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
68 * When either the index or the working directory is different from HEAD an
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
{
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
);
92 private Set
<IResource
> resources
= new LinkedHashSet
<IResource
>();
94 public void refsChanged(RefsChangedEvent e
) {
98 public void indexChanged(IndexChangedEvent 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())
109 for (RepositoryMapping m
: ms
) {
110 repositoryChanged(m
);
114 public void repositoryChanged(final RepositoryMapping which
) {
115 synchronized (resources
) {
116 resources
.add(which
.getContainer());
122 protected IStatus
run(IProgressMonitor arg0
) {
124 if (resources
.size() > 0) {
126 synchronized(resources
) {
127 Iterator
<IResource
> i
= resources
.iterator();
131 while (!m
.isAccessible()) {
133 return Status
.OK_STATUS
;
138 if (resources
.size() > 0)
141 ISchedulingRule markerRule
= m
.getWorkspace().getRuleFactory().markerRule(m
);
142 getJobManager().beginRule(markerRule
, arg0
);
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
);
152 IResource
.DEPTH_INFINITE
,
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
) {
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
176 synchronized (resources
) {
185 } catch (Exception e
) {
186 Activator
.logError("Problem during decorations. Stopped", e
);
192 for (IProject p
: ResourcesPlugin
.getWorkspace().getRoot().getProjects()) {
193 synchronized (resources
) {
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();
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.
220 * Can be invoked any any thread. If the current thread is not the UI
221 * thread, an async update will be scheduled.
224 public static void refresh() {
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
;
239 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY
= new QualifiedName(
240 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
243 static final int UNCHANGED
= 0;
245 static final int CHANGED
= 1;
247 private Boolean
isDirty(IResource rsrc
) {
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())
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
270 } catch (IOException e
) {
271 // TODO Auto-generated catch block
277 public void decorate(final Object element
, final IDecoration decoration
) {
278 final IResource rsrc
= toIResource(element
);
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
);
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?
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
);
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
);
309 if (f
.booleanValue()) {
310 decoration
.addPrefix(">"); // Have not
312 orState(rsrc
, CHANGED
);
314 orState(rsrc
, UNCHANGED
);
315 // decoration.addSuffix("=?");
318 decoration
.addSuffix(" ?* ");
321 if (rsrc
instanceof IProject
) {
322 Repository repo
= mapped
.getRepository();
324 String branch
= repo
.getBranch();
325 if (repo
.isStGitMode()) {
326 String patch
= repo
.getPatch();
327 decoration
.addSuffix(" [StGit " + patch
+ "@" + branch
330 RepositoryState repositoryState
= repo
.getRepositoryState();
332 if (repositoryState
.equals(RepositoryState
.SAFE
))
335 statename
= repositoryState
.getDescription() + " ";
336 decoration
.addSuffix(" [Git " + statename
+ "@ " + branch
+ "]");
338 } catch (IOException e
) {
340 decoration
.addSuffix(" [Git ?]");
342 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
346 if (Team
.isIgnoredHint(rsrc
)) {
347 decoration
.addSuffix("(ignored)");
349 decoration
.addPrefix(">");
350 decoration
.addSuffix("(untracked)");
351 orState(rsrc
.getParent(), CHANGED
);
355 if (!(rsrc
instanceof IContainer
)) {
356 decoration
.addSuffix("(deprecated)"); // Will drop on
358 decoration
.addOverlay(UIIcons
.OVR_PENDING_REMOVE
);
359 orState(rsrc
.getParent(), CHANGED
);
363 if (entry
.getStage() != GitIndex
.STAGE_0
) {
364 decoration
.addSuffix("(conflict)");
365 decoration
.addOverlay(UIIcons
.OVR_CONFLICT
);
366 orState(rsrc
.getParent(), CHANGED
);
371 decoration
.addOverlay(UIIcons
.OVR_PENDING_ADD
);
372 orState(rsrc
.getParent(), CHANGED
);
375 if (entry
.isAssumedValid()) {
376 decoration
.addOverlay(UIIcons
.OVR_ASSUMEVALID
);
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
);
387 if (!entry
.getObjectId().equals(blob
.getId()))
388 decoration
.addPrefix(">");
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
) {
413 Integer dirty
= (Integer
) rsrc
.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
414 Runnable runnable
= new Runnable() {
416 // Async could be called after a
417 // project is closed or a
418 // resource is deleted
419 if (!rsrc
.isAccessible())
421 fireLabelProviderChanged(new LabelProviderChangedEvent(
422 GitResourceDecorator
.this, rsrc
));
426 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, new Integer(flag
));
427 orState(rsrc
.getParent(), flag
);
428 // if (Thread.currentThread() == Display.getDefault().getThread())
431 Display
.getDefault().asyncExec(runnable
);
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())
440 Display
.getDefault().asyncExec(runnable
);
443 } catch (CoreException e
) {
444 // TODO Auto-generated catch block
450 public boolean isLabelProperty(Object element
, String property
) {
451 return super.isLabelProperty(element
, property
);