Use Constant.HEAD for "HEAD" references
[egit/imyousuf.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitResourceDecorator.java
blobc3ae52da9b69cdd1d4064bcaefa7d99227db4ffd
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;
17 import java.util.Set;
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;
62 /**
63 * Supplies annotations for displayed resources.
64 * <p>
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
67 * repository.
69 * When either the index or the working directory is different from HEAD an
70 * indicator is set.
72 * </p>
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 {
80 ResCL() {
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);
90 return null;
93 private Set<IResource> resources = new LinkedHashSet<IResource>();
95 public void refsChanged(RefsChangedEvent e) {
96 repositoryChanged(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())
108 ms.add(mapping);
110 for (RepositoryMapping m : ms) {
111 repositoryChanged(m);
115 public void repositoryChanged(final RepositoryMapping which) {
116 synchronized (resources) {
117 resources.add(which.getContainer());
119 schedule();
122 @Override
123 protected IStatus run(IProgressMonitor arg0) {
124 try {
125 if (resources.size() > 0) {
126 IResource m;
127 synchronized(resources) {
128 Iterator<IResource> i = resources.iterator();
129 m = i.next();
130 i.remove();
132 while (!m.isAccessible()) {
133 if (!i.hasNext())
134 return Status.OK_STATUS;
135 m = i.next();
136 i.remove();
139 if (resources.size() > 0)
140 schedule();
142 ISchedulingRule markerRule = m.getWorkspace().getRuleFactory().markerRule(m);
143 getJobManager().beginRule(markerRule, arg0);
144 try {
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);
150 return true;
153 IResource.DEPTH_INFINITE,
154 true);
155 } finally {
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) {
169 return;
171 try {
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
176 .getParent()) {
177 synchronized (resources) {
178 resources.add(r);
181 return true;
184 true
186 } catch (Exception e) {
187 Activator.logError("Problem during decorations. Stopped", e);
189 schedule();
192 void force() {
193 for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
194 synchronized (resources) {
195 resources.add(p);
198 schedule();
200 } // End ResCL
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();
211 static {
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.
220 * <p>
221 * Can be invoked any any thread. If the current thread is not the UI
222 * thread, an async update will be scheduled.
223 * </p>
225 public static void refresh() {
226 myrescl.force();
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;
237 return null;
240 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName(
241 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
242 "dirty");
244 static final int UNCHANGED = 0;
246 static final int CHANGED = 1;
248 private Boolean isDirty(IResource rsrc) {
249 try {
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())
260 return Boolean.TRUE;
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
270 e.printStackTrace();
271 } catch (IOException e) {
272 // TODO Auto-generated catch block
273 e.printStackTrace();
275 return null;
278 public void decorate(final Object element, final IDecoration decoration) {
279 final IResource rsrc = toIResource(element);
280 if (rsrc == null)
281 return;
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);
287 return;
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?
294 try {
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);
302 if (entry == null) {
303 if (blob == null) {
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);
309 if (f != null) {
310 if (f.booleanValue()) {
311 decoration.addPrefix(">"); // Have not
312 // seen
313 orState(rsrc, CHANGED);
314 } else {
315 orState(rsrc, UNCHANGED);
316 // decoration.addSuffix("=?");
318 } else {
319 decoration.addSuffix(" ?* ");
322 if (rsrc instanceof IProject) {
323 Repository repo = mapped.getRepository();
324 try {
325 String branch = repo.getBranch();
326 RepositoryState repositoryState = repo.getRepositoryState();
327 String statename;
328 if (repositoryState.equals(RepositoryState.SAFE))
329 statename = "";
330 else
331 statename = repositoryState.getDescription() + " ";
332 decoration.addSuffix(" [Git " + statename + "@ " + branch + "]");
333 } catch (IOException e) {
334 e.printStackTrace();
335 decoration.addSuffix(" [Git ?]");
337 decoration.addOverlay(UIIcons.OVR_SHARED);
340 } else {
341 if (Team.isIgnoredHint(rsrc)) {
342 decoration.addSuffix("(ignored)");
343 } else {
344 decoration.addPrefix(">");
345 decoration.addSuffix("(untracked)");
346 orState(rsrc.getParent(), CHANGED);
349 } else {
350 if (!(rsrc instanceof IContainer)) {
351 decoration.addSuffix("(deprecated)"); // Will drop on
352 // commit
353 decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE);
354 orState(rsrc.getParent(), CHANGED);
357 } else {
358 if (entry.getStage() != GitIndex.STAGE_0) {
359 decoration.addSuffix("(conflict)");
360 decoration.addOverlay(UIIcons.OVR_CONFLICT);
361 orState(rsrc.getParent(), CHANGED);
362 return;
365 if (blob == null) {
366 decoration.addOverlay(UIIcons.OVR_PENDING_ADD);
367 orState(rsrc.getParent(), CHANGED);
368 } else {
370 if (entry.isAssumedValid()) {
371 decoration.addOverlay(UIIcons.OVR_ASSUMEVALID);
372 return;
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);
381 } else {
382 if (!entry.getObjectId().equals(blob.getId()))
383 decoration.addPrefix(">");
384 else
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) {
404 return;
407 try {
408 Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
409 Runnable runnable = new Runnable() {
410 public void run() {
411 // Async could be called after a
412 // project is closed or a
413 // resource is deleted
414 if (!rsrc.isAccessible())
415 return;
416 fireLabelProviderChanged(new LabelProviderChangedEvent(
417 GitResourceDecorator.this, rsrc));
420 if (dirty == null) {
421 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, new Integer(flag));
422 orState(rsrc.getParent(), flag);
423 // if (Thread.currentThread() == Display.getDefault().getThread())
424 // runnable.run();
425 // else
426 Display.getDefault().asyncExec(runnable);
427 } else {
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())
433 // runnable.run();
434 // else
435 Display.getDefault().asyncExec(runnable);
438 } catch (CoreException e) {
439 // TODO Auto-generated catch block
440 e.printStackTrace();
444 @Override
445 public boolean isLabelProperty(Object element, String property) {
446 return super.isLabelProperty(element, property);