Use the new git index
[egit.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitResourceDecorator.java
blobd6df4567552bf113738a3ed94dd86d7e9cb49222
1 /*
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;
53 /**
54 * Supplies annotations for displayed resources.
55 * <p>
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
58 * repository.
60 * When either the index or the working directory is different from HEAD an
61 * indicator is set.
63 * </p>
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");
75 requested = false;
76 PlatformUI.getWorkbench().getDecoratorManager().update(
77 GitResourceDecorator.class.getName());
80 public void repositoryChanged(final RepositoryMapping which) {
81 try {
82 which.getContainer().accept(new IResourceVisitor() {
83 public boolean visit(IResource resource) throws CoreException {
84 if (resource instanceof IContainer)
85 clearDecorationState(resource);
86 return true;
88 });
89 } catch (CoreException e) {
90 // TODO Auto-generated catch block
91 e.printStackTrace();
93 start();
96 synchronized void start() {
97 if (requested)
98 return;
99 final Display d = PlatformUI.getWorkbench().getDisplay();
100 if (d.getThread() == Thread.currentThread())
101 run();
102 else {
103 requested = true;
104 d.asyncExec(this);
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) {
115 return;
117 Activator.trace("CLEARING:"+event.getDelta().getResource().getFullPath().toOSString());
118 try {
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
125 .getParent()) {
126 try {
127 // Activator.trace("VCLEARING:"+r.getFullPath().toOSString());
128 clearDecorationState(r);
129 } catch (CoreException e) {
130 // TODO Auto-generated catch block
131 e.printStackTrace();
134 return true;
138 } catch (CoreException e2) {
139 // TODO Auto-generated catch block
140 e2.printStackTrace();
141 return;
143 myrcl.start();
147 public static void clearDecorationState(IResource r) throws CoreException {
148 if (r.isAccessible())
149 r.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, null);
152 static ResCL myrescl = new ResCL();
154 static {
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.
162 * <p>
163 * Can be invoked any any thread. If the current thread is not the UI
164 * thread, an async update will be scheduled.
165 * </p>
167 public static void refresh() {
168 myrcl.start();
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;
179 return null;
182 static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName(
183 "org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
184 "dirty");
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;
194 if (prefix != null)
195 repoRelativePath = prefix + "/" + projectRelativePath;
196 else
197 repoRelativePath = projectRelativePath;
198 return repoRelativePath;
201 private Boolean isDirty(IResource rsrc) {
202 // Activator.trace("isDirty(" + rsrc.getFullPath().toOSString() +")");
203 try {
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())
219 return Boolean.TRUE;
221 return Boolean.FALSE;
223 Tree headTree = repository.mapTree("HEAD");
224 TreeEntry blob = headTree.findBlobMember(repoRelativePath);
225 Entry entry = index.getEntry(repoRelativePath);
226 if (entry == null)
227 return Boolean.TRUE; // flags new resources as changes
228 if (blob == null)
229 return Boolean.TRUE; // added in index
230 return !entry.getObjectId().equals(blob.getId())
231 || entry.isModified(repository.getDirectory()
232 .getParentFile());
234 return null; // not mapped
235 } catch (CoreException e) {
236 // TODO Auto-generated catch block
237 e.printStackTrace();
238 } catch (IOException e) {
239 // TODO Auto-generated catch block
240 e.printStackTrace();
242 return null;
245 public void decorate(final Object element, final IDecoration decoration) {
246 final IResource rsrc = toIResource(element);
247 if (rsrc == null)
248 return;
250 final GitProjectData d = GitProjectData.get(rsrc.getProject());
251 if (d == null)
252 return;
254 Activator.trace("decorate: " + element);
256 RepositoryMapping mapped = d.getRepositoryMapping(rsrc);
257 if (mapped != null) {
258 Repository repo = mapped.getRepository();
259 try {
260 String branch = repo.getBranch();
261 if (repo.isStGitMode()) {
262 String patch = repo.getPatch();
263 decoration.addSuffix(" [StGit " + patch + "@" + branch
264 + "]");
265 } else {
266 decoration.addSuffix(" [Git @ " + branch + "]");
268 } catch (IOException e) {
269 e.printStackTrace();
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?
277 try {
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);
286 if (entry == null) {
287 if (blob == null) {
288 if (rsrc instanceof IContainer) {
289 Integer df = (Integer) rsrc
290 .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
291 Boolean f = df == null ? isDirty(rsrc) : df
292 .intValue() == CHANGED;
293 if (f != null) {
294 if (f.booleanValue()) {
295 decoration.addPrefix(">"); // Have not
296 // seen
297 orState(rsrc, CHANGED);
298 } else {
299 orState(rsrc, UNCHANGED);
300 // decoration.addSuffix("=?");
302 } else {
303 decoration.addSuffix(" ?* ");
305 } else {
306 if (rsrc.getType() == IResource.FILE
307 && Team.isIgnored((IFile) rsrc)) {
308 decoration.addSuffix("(ignored)");
309 } else {
310 decoration.addPrefix(">");
311 decoration.addSuffix("(untracked)");
312 orState(rsrc.getParent(), CHANGED);
315 } else {
316 decoration.addSuffix("(deprecated)"); // Will drop on
317 // commit
318 decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE);
319 orState(rsrc.getParent(), CHANGED);
321 } else {
322 if (blob == null) {
323 decoration.addOverlay(UIIcons.OVR_PENDING_ADD);
324 orState(rsrc.getParent(), CHANGED);
325 } else {
326 decoration.addOverlay(UIIcons.OVR_SHARED);
328 if (entry.isModified(repository.getDirectory()
329 .getParentFile())) {
330 decoration.addPrefix(">");
331 orState(rsrc.getParent(), CHANGED);
332 } else {
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
346 e.printStackTrace();
347 throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
351 private void orState(final IResource rsrc, int flag) {
352 // Activator.trace("orState "+rsrc.getFullPath().toOSString()+
353 // ","+flag);
354 if (rsrc == null || rsrc.getType() == IResource.ROOT) {
355 return;
358 try {
359 Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
360 if (dirty == null) {
361 rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, flag);
362 Activator.trace("SETTING:"+rsrc.getFullPath().toOSString()+" => "+flag);
363 orState(rsrc.getParent(), flag);
364 Display.getDefault().asyncExec(new Runnable() {
365 public void run() {
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())
371 return;
372 fireLabelProviderChanged(new LabelProviderChangedEvent(
373 GitResourceDecorator.this, rsrc));
376 } else {
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() {
383 public void run() {
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())
389 return;
390 fireLabelProviderChanged(new LabelProviderChangedEvent(
391 GitResourceDecorator.this, rsrc));
396 } catch (CoreException e) {
397 // TODO Auto-generated catch block
398 e.printStackTrace();
402 @Override
403 public boolean isLabelProperty(Object element, String property) {
404 Activator.trace("isLabelProperty("+element+","+property+")");
405 return super.isLabelProperty(element, property);