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
.Constants
;
51 import org
.spearce
.jgit
.lib
.GitIndex
;
52 import org
.spearce
.jgit
.lib
.IndexChangedEvent
;
53 import org
.spearce
.jgit
.lib
.RefsChangedEvent
;
54 import org
.spearce
.jgit
.lib
.Repository
;
55 import org
.spearce
.jgit
.lib
.RepositoryChangedEvent
;
56 import org
.spearce
.jgit
.lib
.RepositoryListener
;
57 import org
.spearce
.jgit
.lib
.RepositoryState
;
58 import org
.spearce
.jgit
.lib
.Tree
;
59 import org
.spearce
.jgit
.lib
.TreeEntry
;
60 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
63 * Supplies annotations for displayed resources.
65 * This decorator provides annotations to indicate the status of each resource
66 * when compared to <code>HEAD</code> as well as the index in the relevant
69 * When either the index or the working directory is different from HEAD an
74 public class GitResourceDecorator
extends LabelProvider
implements
75 ILightweightLabelDecorator
{
77 static final String decoratorId
= "org.spearce.egit.ui.internal.decorators.GitResourceDecorator";
78 static class ResCL
extends Job
implements IResourceChangeListener
, RepositoryChangeListener
, RepositoryListener
{
81 super("Git resource decorator trigger");
84 GitResourceDecorator
getActiveDecorator() {
85 IDecoratorManager decoratorManager
= Activator
.getDefault()
86 .getWorkbench().getDecoratorManager();
87 if (decoratorManager
.getEnabled(decoratorId
))
88 return (GitResourceDecorator
) decoratorManager
89 .getLightweightLabelDecorator(decoratorId
);
93 private Set
<IResource
> resources
= new LinkedHashSet
<IResource
>();
95 public void refsChanged(RefsChangedEvent e
) {
99 public void indexChanged(IndexChangedEvent e
) {
100 repositoryChanged(e
);
103 private void repositoryChanged(RepositoryChangedEvent e
) {
104 Set
<RepositoryMapping
> ms
= new HashSet
<RepositoryMapping
>();
105 for (IProject p
: ResourcesPlugin
.getWorkspace().getRoot().getProjects()) {
106 RepositoryMapping mapping
= RepositoryMapping
.getMapping(p
);
107 if (mapping
!= null && mapping
.getRepository() == e
.getRepository())
110 for (RepositoryMapping m
: ms
) {
111 repositoryChanged(m
);
115 public void repositoryChanged(final RepositoryMapping which
) {
116 synchronized (resources
) {
117 resources
.add(which
.getContainer());
123 protected IStatus
run(IProgressMonitor arg0
) {
125 if (resources
.size() > 0) {
127 synchronized(resources
) {
128 Iterator
<IResource
> i
= resources
.iterator();
132 while (!m
.isAccessible()) {
134 return Status
.OK_STATUS
;
139 if (resources
.size() > 0)
142 ISchedulingRule markerRule
= m
.getWorkspace().getRuleFactory().markerRule(m
);
143 getJobManager().beginRule(markerRule
, arg0
);
145 m
.accept(new IResourceVisitor() {
146 public boolean visit(IResource resource
) throws CoreException
{
147 GitResourceDecorator decorator
= getActiveDecorator();
148 if (decorator
!= null)
149 decorator
.clearDecorationState(resource
);
153 IResource
.DEPTH_INFINITE
,
156 getJobManager().endRule(markerRule
);
159 return Status
.OK_STATUS
;
160 } catch (Exception e
) {
161 // We must be silent here or the UI will panic with lots of error messages
162 Activator
.logError("Failed to trigger resource re-decoration", e
);
163 return Status
.OK_STATUS
;
167 public void resourceChanged(IResourceChangeEvent event
) {
168 if (event
.getType() != IResourceChangeEvent
.POST_CHANGE
) {
172 event
.getDelta().accept(new IResourceDeltaVisitor() {
173 public boolean visit(IResourceDelta delta
)
174 throws CoreException
{
175 for (IResource r
= delta
.getResource(); r
.getType() != IResource
.ROOT
; r
= r
177 synchronized (resources
) {
186 } catch (Exception e
) {
187 Activator
.logError("Problem during decorations. Stopped", e
);
193 for (IProject p
: ResourcesPlugin
.getWorkspace().getRoot().getProjects()) {
194 synchronized (resources
) {
202 void clearDecorationState(IResource r
) throws CoreException
{
203 if (r
.isAccessible()) {
204 r
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, null);
205 fireLabelProviderChanged(new LabelProviderChangedEvent(this, r
));
209 static ResCL myrescl
= new ResCL();
212 Repository
.addAnyRepositoryChangedListener(myrescl
);
213 GitProjectData
.addRepositoryChangeListener(myrescl
);
214 ResourcesPlugin
.getWorkspace().addResourceChangeListener(myrescl
,
215 IResourceChangeEvent
.POST_CHANGE
);
219 * Request that the decorator be updated, to reflect any recent changes.
221 * Can be invoked any any thread. If the current thread is not the UI
222 * thread, an async update will be scheduled.
225 public static void refresh() {
229 private static IResource
toIResource(final Object e
) {
230 if (e
instanceof IResource
)
231 return (IResource
) e
;
232 if (e
instanceof IAdaptable
) {
233 final Object c
= ((IAdaptable
) e
).getAdapter(IResource
.class);
234 if (c
instanceof IResource
)
235 return (IResource
) c
;
240 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY
= new QualifiedName(
241 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
244 static final int UNCHANGED
= 0;
246 static final int CHANGED
= 1;
248 private Boolean
isDirty(IResource rsrc
) {
250 if (rsrc
.getType() == IResource
.FILE
&& Team
.isIgnored((IFile
)rsrc
))
251 return Boolean
.FALSE
;
253 RepositoryMapping mapped
= RepositoryMapping
.getMapping(rsrc
);
254 if (mapped
!= null) {
255 if (rsrc
instanceof IContainer
) {
256 for (IResource r
: ((IContainer
) rsrc
)
257 .members(IContainer
.EXCLUDE_DERIVED
)) {
258 Boolean f
= isDirty(r
);
259 if (f
== null || f
.booleanValue())
262 return Boolean
.FALSE
;
265 return Boolean
.valueOf(mapped
.isResourceChanged(rsrc
));
267 return null; // not mapped
268 } catch (CoreException e
) {
269 // TODO Auto-generated catch block
271 } catch (IOException e
) {
272 // TODO Auto-generated catch block
278 public void decorate(final Object element
, final IDecoration decoration
) {
279 final IResource rsrc
= toIResource(element
);
283 // If the workspace has not been refreshed properly a resource might
284 // not actually exist, so we ignore these and do not decorate them
285 if (!rsrc
.exists() && !rsrc
.isPhantom()) {
286 Activator
.trace("Tried to decorate non-existent resource "+rsrc
);
290 RepositoryMapping mapped
= RepositoryMapping
.getMapping(rsrc
);
292 // TODO: How do I see a renamed resource?
293 // TODO: Even trickier: when a path change from being blob to tree?
295 if (mapped
!= null) {
296 Repository repository
= mapped
.getRepository();
297 GitIndex index
= repository
.getIndex();
298 String repoRelativePath
= mapped
.getRepoRelativePath(rsrc
);
299 Tree headTree
= repository
.mapTree(Constants
.HEAD
);
300 TreeEntry blob
= headTree
!=null ? headTree
.findBlobMember(repoRelativePath
) : null;
301 Entry entry
= index
.getEntry(repoRelativePath
);
304 if (rsrc
instanceof IContainer
) {
305 Integer df
= (Integer
) rsrc
306 .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
307 Boolean f
= df
== null ?
isDirty(rsrc
)
308 : Boolean
.valueOf(df
.intValue() == CHANGED
);
310 if (f
.booleanValue()) {
311 decoration
.addPrefix(">"); // Have not
313 orState(rsrc
, CHANGED
);
315 orState(rsrc
, UNCHANGED
);
316 // decoration.addSuffix("=?");
319 decoration
.addSuffix(" ?* ");
322 if (rsrc
instanceof IProject
) {
323 Repository repo
= mapped
.getRepository();
325 String branch
= repo
.getBranch();
326 RepositoryState repositoryState
= repo
.getRepositoryState();
328 if (repositoryState
.equals(RepositoryState
.SAFE
))
331 statename
= repositoryState
.getDescription() + " ";
332 decoration
.addSuffix(" [Git " + statename
+ "@ " + branch
+ "]");
333 } catch (IOException e
) {
335 decoration
.addSuffix(" [Git ?]");
337 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
341 if (Team
.isIgnoredHint(rsrc
)) {
342 decoration
.addSuffix("(ignored)");
344 decoration
.addPrefix(">");
345 decoration
.addSuffix("(untracked)");
346 orState(rsrc
.getParent(), CHANGED
);
350 if (!(rsrc
instanceof IContainer
)) {
351 decoration
.addSuffix("(deprecated)"); // Will drop on
353 decoration
.addOverlay(UIIcons
.OVR_PENDING_REMOVE
);
354 orState(rsrc
.getParent(), CHANGED
);
358 if (entry
.getStage() != GitIndex
.STAGE_0
) {
359 decoration
.addSuffix("(conflict)");
360 decoration
.addOverlay(UIIcons
.OVR_CONFLICT
);
361 orState(rsrc
.getParent(), CHANGED
);
366 decoration
.addOverlay(UIIcons
.OVR_PENDING_ADD
);
367 orState(rsrc
.getParent(), CHANGED
);
370 if (entry
.isAssumedValid()) {
371 decoration
.addOverlay(UIIcons
.OVR_ASSUMEVALID
);
375 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
377 if (entry
.isModified(mapped
.getWorkDir(), true)) {
378 decoration
.addPrefix(">");
379 decoration
.addSuffix("(not updated)");
380 orState(rsrc
.getParent(), CHANGED
);
382 if (!entry
.getObjectId().equals(blob
.getId()))
383 decoration
.addPrefix(">");
385 decoration
.addPrefix(""); // set it to avoid further calls
390 } catch (IOException e
) {
391 decoration
.addSuffix("?");
392 // If we throw an exception Eclipse will log the error and
393 // unregister us thereby preventing us from dragging down the
394 // entire workbench because we are crashing.
396 throw new RuntimeException(UIText
.Decorator_failedLazyLoading
, e
);
397 } catch (CoreException e
) {
398 throw new RuntimeException(UIText
.Decorator_failedLazyLoading
, e
);
402 private void orState(final IResource rsrc
, int flag
) {
403 if (rsrc
== null || rsrc
.getType() == IResource
.ROOT
) {
408 Integer dirty
= (Integer
) rsrc
.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
409 Runnable runnable
= new Runnable() {
411 // Async could be called after a
412 // project is closed or a
413 // resource is deleted
414 if (!rsrc
.isAccessible())
416 fireLabelProviderChanged(new LabelProviderChangedEvent(
417 GitResourceDecorator
.this, rsrc
));
421 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, new Integer(flag
));
422 orState(rsrc
.getParent(), flag
);
423 // if (Thread.currentThread() == Display.getDefault().getThread())
426 Display
.getDefault().asyncExec(runnable
);
428 if ((dirty
.intValue() | flag
) != dirty
.intValue()) {
429 dirty
= new Integer(dirty
.intValue() | flag
);
430 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, dirty
);
431 orState(rsrc
.getParent(), dirty
.intValue());
432 // if (Thread.currentThread() == Display.getDefault().getThread())
435 Display
.getDefault().asyncExec(runnable
);
438 } catch (CoreException e
) {
439 // TODO Auto-generated catch block
445 public boolean isLabelProperty(Object element
, String property
) {
446 return super.isLabelProperty(element
, property
);