2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
3 * Copyright (C) 2007 Robin Rosenberg <robin.rosenberg@dewire.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License, version 2.1, as published by the Free Software Foundation.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18 package org
.spearce
.egit
.ui
.internal
.decorators
;
20 import java
.io
.IOException
;
22 import org
.eclipse
.core
.resources
.IContainer
;
23 import org
.eclipse
.core
.resources
.IFile
;
24 import org
.eclipse
.core
.resources
.IResource
;
25 import org
.eclipse
.core
.resources
.IResourceChangeEvent
;
26 import org
.eclipse
.core
.resources
.IResourceChangeListener
;
27 import org
.eclipse
.core
.resources
.IResourceDelta
;
28 import org
.eclipse
.core
.resources
.IResourceDeltaVisitor
;
29 import org
.eclipse
.core
.resources
.IResourceVisitor
;
30 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
31 import org
.eclipse
.core
.runtime
.CoreException
;
32 import org
.eclipse
.core
.runtime
.IAdaptable
;
33 import org
.eclipse
.core
.runtime
.QualifiedName
;
34 import org
.eclipse
.jface
.viewers
.IDecoration
;
35 import org
.eclipse
.jface
.viewers
.ILightweightLabelDecorator
;
36 import org
.eclipse
.jface
.viewers
.LabelProvider
;
37 import org
.eclipse
.jface
.viewers
.LabelProviderChangedEvent
;
38 import org
.eclipse
.swt
.widgets
.Display
;
39 import org
.eclipse
.team
.core
.Team
;
40 import org
.eclipse
.ui
.PlatformUI
;
41 import org
.spearce
.egit
.core
.project
.GitProjectData
;
42 import org
.spearce
.egit
.core
.project
.RepositoryChangeListener
;
43 import org
.spearce
.egit
.core
.project
.RepositoryMapping
;
44 import org
.spearce
.egit
.ui
.Activator
;
45 import org
.spearce
.egit
.ui
.UIIcons
;
46 import org
.spearce
.egit
.ui
.UIText
;
47 import org
.spearce
.jgit
.lib
.GitIndex
;
48 import org
.spearce
.jgit
.lib
.Repository
;
49 import org
.spearce
.jgit
.lib
.Tree
;
50 import org
.spearce
.jgit
.lib
.TreeEntry
;
51 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
54 * Supplies annotations for displayed resources.
56 * This decorator provides annotations to indicate the status of each resource
57 * when compared to <code>HEAD</code> as well as the index in the relevant
60 * When either the index or the working directory is different from HEAD an
65 public class GitResourceDecorator
extends LabelProvider
implements
66 ILightweightLabelDecorator
{
68 private static final RCL myrcl
= new RCL();
70 static class RCL
implements RepositoryChangeListener
, Runnable
{
71 private boolean requested
;
73 public synchronized void run() {
74 Activator
.trace("Invoking decorator");
76 PlatformUI
.getWorkbench().getDecoratorManager().update(
77 GitResourceDecorator
.class.getName());
80 public void repositoryChanged(final RepositoryMapping which
) {
82 which
.getContainer().accept(new IResourceVisitor() {
83 public boolean visit(IResource resource
) throws CoreException
{
84 if (resource
instanceof IContainer
)
85 clearDecorationState(resource
);
89 } catch (CoreException e
) {
90 // TODO Auto-generated catch block
96 synchronized void start() {
99 final Display d
= PlatformUI
.getWorkbench().getDisplay();
100 if (d
.getThread() == Thread
.currentThread())
109 static class ResCL
implements IResourceChangeListener
{
110 public void resourceChanged(IResourceChangeEvent event
) {
111 Activator
.trace("resourceChanged(buildKind="
112 + event
.getBuildKind() + ",type=" + event
.getType()
113 + ",source=" + event
.getSource());
114 if (event
.getType() != IResourceChangeEvent
.POST_CHANGE
) {
117 Activator
.trace("CLEARING:"+event
.getDelta().getResource().getFullPath().toOSString());
119 event
.getDelta().accept(new IResourceDeltaVisitor() {
121 public boolean visit(IResourceDelta delta
)
122 throws CoreException
{
123 Activator
.trace("VCLEARING:"+delta
.getResource().getFullPath().toOSString());
124 for (IResource r
= delta
.getResource(); r
.getType() != IResource
.ROOT
; r
= r
127 // Activator.trace("VCLEARING:"+r.getFullPath().toOSString());
128 clearDecorationState(r
);
129 } catch (CoreException e
) {
130 // TODO Auto-generated catch block
138 } catch (CoreException e2
) {
139 // TODO Auto-generated catch block
140 e2
.printStackTrace();
147 public static void clearDecorationState(IResource r
) throws CoreException
{
148 if (r
.isAccessible())
149 r
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, null);
152 static ResCL myrescl
= new ResCL();
155 GitProjectData
.addRepositoryChangeListener(myrcl
);
156 ResourcesPlugin
.getWorkspace().addResourceChangeListener(myrescl
,
157 IResourceChangeEvent
.POST_CHANGE
);
161 * Request that the decorator be updated, to reflect any recent changes.
163 * Can be invoked any any thread. If the current thread is not the UI
164 * thread, an async update will be scheduled.
167 public static void refresh() {
171 private static IResource
toIResource(final Object e
) {
172 if (e
instanceof IResource
)
173 return (IResource
) e
;
174 if (e
instanceof IAdaptable
) {
175 final Object c
= ((IAdaptable
) e
).getAdapter(IResource
.class);
176 if (c
instanceof IResource
)
177 return (IResource
) c
;
182 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY
= new QualifiedName(
183 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
186 static final int UNCHANGED
= 0;
188 static final int CHANGED
= 1;
190 private String
getRepoRelativePath(IResource rsrc
, RepositoryMapping mapped
) {
191 String prefix
= mapped
.getSubset();
192 String projectRelativePath
= rsrc
.getProjectRelativePath().toString();
193 String repoRelativePath
;
195 repoRelativePath
= prefix
+ "/" + projectRelativePath
;
197 repoRelativePath
= projectRelativePath
;
198 return repoRelativePath
;
201 private Boolean
isDirty(IResource rsrc
) {
202 // Activator.trace("isDirty(" + rsrc.getFullPath().toOSString() +")");
204 if (rsrc
.getType() == IResource
.FILE
&& Team
.isIgnored((IFile
)rsrc
))
205 return Boolean
.FALSE
;
207 final GitProjectData d
= GitProjectData
.get(rsrc
.getProject());
208 RepositoryMapping mapped
= d
209 .getRepositoryMapping(rsrc
.getProject());
210 if (mapped
!= null) {
211 Repository repository
= mapped
.getRepository();
212 GitIndex index
= repository
.getIndex();
213 String repoRelativePath
= getRepoRelativePath(rsrc
, mapped
);
214 if (rsrc
instanceof IContainer
) {
215 for (IResource r
: ((IContainer
) rsrc
)
216 .members(IContainer
.EXCLUDE_DERIVED
)) {
217 Boolean f
= isDirty(r
);
218 if (f
== null || f
.booleanValue())
221 return Boolean
.FALSE
;
223 Tree headTree
= repository
.mapTree("HEAD");
224 TreeEntry blob
= headTree
.findBlobMember(repoRelativePath
);
225 Entry entry
= index
.getEntry(repoRelativePath
);
227 return Boolean
.TRUE
; // flags new resources as changes
229 return Boolean
.TRUE
; // added in index
230 return !entry
.getObjectId().equals(blob
.getId())
231 || entry
.isModified(repository
.getDirectory()
234 return null; // not mapped
235 } catch (CoreException e
) {
236 // TODO Auto-generated catch block
238 } catch (IOException e
) {
239 // TODO Auto-generated catch block
245 public void decorate(final Object element
, final IDecoration decoration
) {
246 final IResource rsrc
= toIResource(element
);
250 final GitProjectData d
= GitProjectData
.get(rsrc
.getProject());
254 Activator
.trace("decorate: " + element
);
256 RepositoryMapping mapped
= d
.getRepositoryMapping(rsrc
);
257 if (mapped
!= null) {
258 Repository repo
= mapped
.getRepository();
260 String branch
= repo
.getBranch();
261 if (repo
.isStGitMode()) {
262 String patch
= repo
.getPatch();
263 decoration
.addSuffix(" [StGit " + patch
+ "@" + branch
266 decoration
.addSuffix(" [Git @ " + branch
+ "]");
268 } catch (IOException e
) {
270 decoration
.addSuffix(" [Git ?]");
272 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
275 // TODO: How do I see a renamed resource?
276 // TODO: Even trickier: when a path change from being blob to tree?
278 mapped
= d
.getRepositoryMapping(rsrc
.getProject());
279 if (mapped
!= null) {
280 Repository repository
= mapped
.getRepository();
281 GitIndex index
= repository
.getIndex();
282 String repoRelativePath
= getRepoRelativePath(rsrc
, mapped
);
283 Tree headTree
= repository
.mapTree("HEAD");
284 TreeEntry blob
= headTree
.findBlobMember(repoRelativePath
);
285 Entry entry
= index
.getEntry(repoRelativePath
);
288 if (rsrc
instanceof IContainer
) {
289 Integer df
= (Integer
) rsrc
290 .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
291 Boolean f
= df
== null ?
isDirty(rsrc
) : df
292 .intValue() == CHANGED
;
294 if (f
.booleanValue()) {
295 decoration
.addPrefix(">"); // Have not
297 orState(rsrc
, CHANGED
);
299 orState(rsrc
, UNCHANGED
);
300 // decoration.addSuffix("=?");
303 decoration
.addSuffix(" ?* ");
306 if (rsrc
.getType() == IResource
.FILE
307 && Team
.isIgnored((IFile
) rsrc
)) {
308 decoration
.addSuffix("(ignored)");
310 decoration
.addPrefix(">");
311 decoration
.addSuffix("(untracked)");
312 orState(rsrc
.getParent(), CHANGED
);
316 decoration
.addSuffix("(deprecated)"); // Will drop on
318 decoration
.addOverlay(UIIcons
.OVR_PENDING_REMOVE
);
319 orState(rsrc
.getParent(), CHANGED
);
323 decoration
.addOverlay(UIIcons
.OVR_PENDING_ADD
);
324 orState(rsrc
.getParent(), CHANGED
);
326 decoration
.addOverlay(UIIcons
.OVR_SHARED
);
328 if (entry
.isModified(repository
.getDirectory()
330 decoration
.addPrefix(">");
331 orState(rsrc
.getParent(), CHANGED
);
333 decoration
.addSuffix(""); // set it to avoid further calls
337 } catch (IOException e
) {
338 decoration
.addSuffix("?");
339 // If we throw an exception Eclipse will log the error and
340 // unregister us thereby preventing us from dragging down the
341 // entire workbench because we are crashing.
343 throw new RuntimeException(UIText
.Decorator_failedLazyLoading
, e
);
344 } catch (CoreException e
) {
345 // TODO Auto-generated catch block
347 throw new RuntimeException(UIText
.Decorator_failedLazyLoading
, e
);
351 private void orState(final IResource rsrc
, int flag
) {
352 // Activator.trace("orState "+rsrc.getFullPath().toOSString()+
354 if (rsrc
== null || rsrc
.getType() == IResource
.ROOT
) {
359 Integer dirty
= (Integer
) rsrc
.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
);
361 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, flag
);
362 Activator
.trace("SETTING:"+rsrc
.getFullPath().toOSString()+" => "+flag
);
363 orState(rsrc
.getParent(), flag
);
364 Display
.getDefault().asyncExec(new Runnable() {
366 Activator
.trace("firing on " + rsrc
);
367 // Async could be called after a
368 // project is closed or a
369 // resource is deleted
370 if (!rsrc
.isAccessible())
372 fireLabelProviderChanged(new LabelProviderChangedEvent(
373 GitResourceDecorator
.this, rsrc
));
377 if ((dirty
.intValue() | flag
) != dirty
.intValue()) {
378 dirty
= dirty
| flag
;
379 rsrc
.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY
, dirty
);
380 Activator
.trace("SETTING:"+rsrc
.getFullPath().toOSString()+" => "+dirty
);
381 orState(rsrc
.getParent(), dirty
.intValue());
382 Display
.getDefault().asyncExec(new Runnable() {
384 Activator
.trace("firing on " + rsrc
);
385 // Async could be called after a
386 // project is closed or a
387 // resource is deleted
388 if (!rsrc
.isAccessible())
390 fireLabelProviderChanged(new LabelProviderChangedEvent(
391 GitResourceDecorator
.this, rsrc
));
396 } catch (CoreException e
) {
397 // TODO Auto-generated catch block
403 public boolean isLabelProperty(Object element
, String property
) {
404 Activator
.trace("isLabelProperty("+element
+","+property
+")");
405 return super.isLabelProperty(element
, property
);