All of lore.kernel.org
 help / color / mirror / Atom feed
* [EGIT PATCH v2 00/12] Support customizable label decorations
@ 2009-02-11 18:40 Tor Arne Vestbø
  2009-02-11 18:40 ` [EGIT PATCH v2 01/12] Add support code to handle plugin property changes Tor Arne Vestbø
  2009-02-16 20:57 ` [EGIT PATCH v2 00/12] Support customizable label decorations Robin Rosenberg
  0 siblings, 2 replies; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

This series adds support for customizable label decorations, which
is usefull for hiding selected decorations, or tweaking the format
of the decoration text.

Changes in v2:

  - Fixed layout issues in preference dialog
  - Icons replaced to match established conventions
  - 'Assume unchanged' icon is back
  - Added tooltips in the preference dialog
  - 'Added and dirty' is now decorated appropriately
  - Refactored container decorations. Now shares code path with
    files for more accurate results, and to allow inheritance

Decorations are edited using the new Team->Git->Label Decorations
preference page, which is based off similar functionality from the
existing CVS and SVN team providers.

Icons can be enabled and disabled individually, and text can be
customized by reordering and editing the set of mapped variables.
Boolean variables like 'dirty' and 'staged' can be customized by
postfixing the variable name with a colon and a selected string
that should be insert if the variable evaluates to true.

The two general options control traversal of child and parent
elements during decoration. The first, 'Also re-decorate...',
controls whether or not ancestor elements of the current decorated
elment will also be scheduled for re-recoration. The second, 
'Maximum number of levels...', controls how deep the container
decoration algorithm will recurse when trying to determine the
state (dirty, staged, etc.) of a container.

Tweaking these options will improve performance for large trees.

The code should work fairly well for most usecases, but I may have
missed cases where the decorations will fail misserably. If so, 
please let me know. 

Known issues are:

  - If a project has a repository more than one level above the
    project directory decorations will fail.

  - When a Java resource is dirty, each package in the package
    hierarcy will appear dirty, also when the layout is set
    to 'flat', which can be confusing.

These are on my list for features to add on top of these series,
but I consider them non-blocking. This also goes for online help,
which will be added when things stabilize some more.

I've also sprinkled the code with TODOs where I found possible 
future improvments. One such improvment is performance, where for
example refactoring to use one shared status cache should help.

Tor Arne



Tor Arne Vestbø (12):
  Add support code to handle plugin property changes
  Use Set instead of array to keep track of change listeners
  Add a specialized team exception for Git
  Add new class ExceptionCollector for grouping exceptions
  Add new class SWTUtils with helper-methods for creating controls
  Implement basic customizable label decorations with preferences
  Add binding for name of the current branch
  Add icon decoration for tracked and untracked resources
  Implement icon and text decorations of various resource states
  Don't decorate every single resource on repository change
  Expose the underlying resource entries in ContainerTreeIterator
  Implement label decorations for folders and projects

 org.spearce.egit.core/META-INF/MANIFEST.MF         |    5 +-
 .../spearce/egit/core/ContainerTreeIterator.java   |   23 +-
 .../src/org/spearce/egit/core/GitException.java    |  168 ++++
 .../core/internal/util/ExceptionCollector.java     |  128 +++
 .../spearce/egit/core/project/GitProjectData.java  |   40 +-
 org.spearce.egit.ui/icons/ovr/assume_valid.gif     |  Bin 0 -> 85 bytes
 org.spearce.egit.ui/icons/ovr/assumevalid.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/conflict.gif         |  Bin 64 -> 194 bytes
 org.spearce.egit.ui/icons/ovr/pending_add.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/pending_remove.gif   |  Bin 111 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/shared.gif           |  Bin 106 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/staged.gif           |  Bin 0 -> 114 bytes
 org.spearce.egit.ui/icons/ovr/staged_added.gif     |  Bin 0 -> 169 bytes
 org.spearce.egit.ui/icons/ovr/staged_removed.gif   |  Bin 0 -> 176 bytes
 org.spearce.egit.ui/icons/ovr/untracked.gif        |  Bin 0 -> 79 bytes
 org.spearce.egit.ui/plugin.properties              |    1 +
 org.spearce.egit.ui/plugin.xml                     |   12 +-
 .../src/org/spearce/egit/ui/Activator.java         |   68 ++
 .../egit/ui/PluginPreferenceInitializer.java       |   15 +
 .../src/org/spearce/egit/ui/UIIcons.java           |   21 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |   21 +
 .../src/org/spearce/egit/ui/UIText.java            |   99 ++-
 .../src/org/spearce/egit/ui/internal/SWTUtils.java |  595 ++++++++++++
 .../egit/ui/internal/actions/BranchAction.java     |    4 +-
 .../egit/ui/internal/actions/Disconnect.java       |    4 +-
 .../egit/ui/internal/actions/ResetAction.java      |    4 +-
 .../decorators/DecoratableResourceAdapter.java     |  391 ++++++++
 .../decorators/GitLightweightDecorator.java        |  653 ++++++++++++++
 .../internal/decorators/GitResourceDecorator.java  |  454 ----------
 .../internal/decorators/IDecoratableResource.java  |  100 ++
 .../preferences/GitDecoratorPreferencePage.java    |  949 ++++++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   38 +-
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    9 +
 33 files changed, 3310 insertions(+), 492 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/GitException.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/internal/util/ExceptionCollector.java
 create mode 100644 org.spearce.egit.ui/icons/ovr/assume_valid.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/assumevalid.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/pending_add.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/pending_remove.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/shared.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged_added.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged_removed.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/untracked.gif
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/SWTUtils.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 01/12] Add support code to handle plugin property changes
  2009-02-11 18:40 [EGIT PATCH v2 00/12] Support customizable label decorations Tor Arne Vestbø
@ 2009-02-11 18:40 ` Tor Arne Vestbø
  2009-02-11 18:40   ` [EGIT PATCH v2 02/12] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
  2009-02-16 20:57 ` [EGIT PATCH v2 00/12] Support customizable label decorations Robin Rosenberg
  1 sibling, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../src/org/spearce/egit/ui/Activator.java         |   52 ++++++++++++++++++++
 1 files changed, 52 insertions(+), 0 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
index fced643..534c408 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
@@ -10,9 +10,11 @@
 
 import java.net.Authenticator;
 import java.net.ProxySelector;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.eclipse.core.net.proxy.IProxyService;
@@ -27,6 +29,8 @@
 import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jsch.core.IJSchService;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
@@ -44,9 +48,24 @@
  * This is a plugin singleton mostly controlling logging.
  */
 public class Activator extends AbstractUIPlugin {
+
+	/**
+	 *  The one and only instance
+	 */
 	private static Activator plugin;
 
 	/**
+	 * Property listeners for plugin specific events
+	 */
+	private static List<IPropertyChangeListener> propertyChangeListeners =
+		new ArrayList<IPropertyChangeListener>(5);
+
+	/**
+	 * Property constant indicating the decorator configuration has changed.
+	 */
+	public static final String DECORATORS_CHANGED = "org.spearce.egit.ui.DECORATORS_CHANGED"; //$NON-NLS-1$
+
+	/**
 	 * @return the {@link Activator} singleton.
 	 */
 	public static Activator getDefault() {
@@ -167,6 +186,39 @@ private void setupRepoIndexRefresh() {
 		Repository.addAnyRepositoryChangedListener(refreshJob);
 	}
 
+	/**
+	 * Register for changes made to Team properties.
+	 * 
+	 * @param listener
+	 *            The listener to register
+	 */
+	public static synchronized void addPropertyChangeListener(
+			IPropertyChangeListener listener) {
+		propertyChangeListeners.add(listener);
+	}
+
+	/**
+	 * Remove a Team property changes.
+	 * 
+	 * @param listener
+	 *            The listener to remove
+	 */
+	public static synchronized void removePropertyChangeListener(
+			IPropertyChangeListener listener) {
+		propertyChangeListeners.remove(listener);
+	}
+
+	/**
+	 * Broadcast a Team property change.
+	 * 
+	 * @param event
+	 *            The event to broadcast
+	 */
+	public static synchronized void broadcastPropertyChange(PropertyChangeEvent event) {
+		for (IPropertyChangeListener listener : propertyChangeListeners)
+			listener.propertyChange(event);
+	}
+
 	static class RIRefresh extends Job implements RepositoryListener {
 
 		RIRefresh() {
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 02/12] Use Set instead of array to keep track of change listeners
  2009-02-11 18:40 ` [EGIT PATCH v2 01/12] Add support code to handle plugin property changes Tor Arne Vestbø
@ 2009-02-11 18:40   ` Tor Arne Vestbø
  2009-02-11 18:40     ` [EGIT PATCH v2 03/12] Add a specialized team exception for Git Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Also, add method for removing listeners.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../spearce/egit/core/project/GitProjectData.java  |   40 ++++++++++++-------
 1 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
index db5f20b..b12a85f 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/project/GitProjectData.java
@@ -52,7 +52,7 @@
 
 	private static final Map<File, WeakReference> repositoryCache = new HashMap<File, WeakReference>();
 
-	private static RepositoryChangeListener[] repositoryChangeListeners = {};
+	private static Set<RepositoryChangeListener> repositoryChangeListeners = new HashSet<RepositoryChangeListener>();
 
 	@SuppressWarnings("synthetic-access")
 	private static final IResourceChangeListener rcl = new RCL();
@@ -112,16 +112,18 @@ public static synchronized void addRepositoryChangeListener(
 			final RepositoryChangeListener objectThatCares) {
 		if (objectThatCares == null)
 			throw new NullPointerException();
-		for (int k = repositoryChangeListeners.length - 1; k >= 0; k--) {
-			if (repositoryChangeListeners[k] == objectThatCares)
-				return;
-		}
-		final int p = repositoryChangeListeners.length;
-		final RepositoryChangeListener[] n;
-		n = new RepositoryChangeListener[p + 1];
-		System.arraycopy(repositoryChangeListeners, 0, n, 0, p);
-		n[p] = objectThatCares;
-		repositoryChangeListeners = n;
+		repositoryChangeListeners.add(objectThatCares);
+	}
+
+	/**
+	 * Remove a registered {@link RepositoryChangeListener}
+	 * 
+	 * @param objectThatCares
+	 *            The listener to remove
+	 */
+	public static synchronized void removeRepositoryChangeListener(
+			final RepositoryChangeListener objectThatCares) {
+		repositoryChangeListeners.remove(objectThatCares);
 	}
 
 	/**
@@ -131,13 +133,21 @@ public static synchronized void addRepositoryChangeListener(
 	 *            the repository which has had changes occur within it.
 	 */
 	static void fireRepositoryChanged(final RepositoryMapping which) {
-		final RepositoryChangeListener[] e = getRepositoryChangeListeners();
-		for (int k = e.length - 1; k >= 0; k--)
-			e[k].repositoryChanged(which);
+		for (RepositoryChangeListener listener : getRepositoryChangeListeners())
+			listener.repositoryChanged(which);
 	}
 
+	/**
+	 * Get a copy of the current set of repository change listeners
+	 * <p>
+	 * The array has no references, so is safe for iteration and modification
+	 * 
+	 * @return a copy of the current repository change listeners
+	 */
 	private static synchronized RepositoryChangeListener[] getRepositoryChangeListeners() {
-		return repositoryChangeListeners;
+		return repositoryChangeListeners
+				.toArray(new RepositoryChangeListener[repositoryChangeListeners
+						.size()]);
 	}
 
 	/**
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 03/12] Add a specialized team exception for Git
  2009-02-11 18:40   ` [EGIT PATCH v2 02/12] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
@ 2009-02-11 18:40     ` Tor Arne Vestbø
  2009-02-11 18:40       ` [EGIT PATCH v2 04/12] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Copied largly from org.eclipse.team.internal.ccvs.core (CVS) and
org.tigris.subversion.subclipse.core (SVN), and then cleaned up.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../src/org/spearce/egit/core/GitException.java    |  168 ++++++++++++++++++++
 1 files changed, 168 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/GitException.java

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/GitException.java b/org.spearce.egit.core/src/org/spearce/egit/core/GitException.java
new file mode 100644
index 0000000..7217fb7
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/GitException.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2003, 2006 Subclipse project and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.core;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.TeamStatus;
+
+/**
+ * A checked exception representing a failure in the Git plugin.
+ * <p>
+ * Git exceptions contain a status object describing the cause of the exception.
+ * </p>
+ * 
+ * @see IStatus
+ */
+public class GitException extends TeamException {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Constructs a new Git exception
+	 * 
+	 * @param severity
+	 * @param code
+	 * @param message
+	 * @param e
+	 */
+	public GitException(int severity, int code, String message, Throwable e) {
+		super(new TeamStatus(severity, Activator.getPluginId(), code, message,
+				e, null));
+	}
+
+	/**
+	 * Constructs a new Git exception
+	 * 
+	 * @param severity
+	 * @param code
+	 * @param message
+	 */
+	public GitException(int severity, int code, String message) {
+		this(severity, code, message, null);
+	}
+
+	/**
+	 * Constructs a new Git exception
+	 * 
+	 * @param message
+	 * @param e
+	 */
+	public GitException(String message, Throwable e) {
+		this(IStatus.ERROR, UNABLE, message, e);
+	}
+
+	/**
+	 * Constructs a new Git exception
+	 * 
+	 * @param message
+	 */
+	public GitException(String message) {
+		this(message, null);
+	}
+
+	/**
+	 * Constructs a new Git exception
+	 * 
+	 * @param status
+	 */
+	public GitException(IStatus status) {
+		super(status);
+	}
+
+	/**
+	 * Transform this exception into a CoreException
+	 * 
+	 * @return the new CoreException
+	 */
+	public CoreException toCoreException() {
+		IStatus status = getStatus();
+		return new CoreException(new Status(status.getSeverity(), status
+				.getPlugin(), 0, status.getMessage(), this));
+	}
+
+	/**
+	 * Static helper method for creating a Git exception
+	 * 
+	 * @param resource
+	 * @param message
+	 * @param e
+	 * @return the created exception
+	 */
+	public static GitException wrapException(IResource resource,
+			String message, CoreException e) {
+		return new GitException(IStatus.ERROR, e.getStatus().getCode(),
+				message, e);
+	}
+
+	/**
+	 * Static helper method for creating a Git exception
+	 * 
+	 * @param e
+	 * @return the created exception
+	 */
+	public static GitException wrapException(Exception e) {
+		Throwable t = e;
+		if (e instanceof InvocationTargetException) {
+			Throwable target = ((InvocationTargetException) e)
+					.getTargetException();
+			if (target instanceof GitException) {
+				return (GitException) target;
+			}
+			t = target;
+		}
+
+		return new GitException(IStatus.ERROR, UNABLE,
+				t.getMessage() != null ? t.getMessage() : "", t); //$NON-NLS-1$
+	}
+
+	/**
+	 * Static helper method for creating a Git exception
+	 * 
+	 * @param e
+	 * @return the created exception
+	 */
+	public static GitException wrapException(CoreException e) {
+		IStatus status = e.getStatus();
+		if (!status.isMultiStatus()) {
+			status = new TeamStatus(status.getSeverity(), Activator
+					.getPluginId(), status.getCode(), status.getMessage(), e,
+					null);
+		}
+		return new GitException(status);
+	}
+
+	/**
+	 * Static helper method for creating a Git exception
+	 * 
+	 * @param e
+	 * @return the created exception
+	 */
+	public static GitException wrapException(IOException e) {
+		return new GitException(IStatus.ERROR, IO_FAILED, e.getMessage(), e);
+	}
+
+	/**
+	 * Static helper method for creating a Git exception
+	 * 
+	 * @param e
+	 * @return the created exception
+	 */
+	public static GitException wrapException(TeamException e) {
+		if (e instanceof GitException)
+			return (GitException) e;
+		else
+			return new GitException(e.getStatus());
+	}
+}
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 04/12] Add new class ExceptionCollector for grouping exceptions
  2009-02-11 18:40     ` [EGIT PATCH v2 03/12] Add a specialized team exception for Git Tor Arne Vestbø
@ 2009-02-11 18:40       ` Tor Arne Vestbø
  2009-02-11 18:40         ` [EGIT PATCH v2 05/12] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Copied from org.eclipse.team.internal.core

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.core/META-INF/MANIFEST.MF         |    5 +-
 .../core/internal/util/ExceptionCollector.java     |  128 ++++++++++++++++++++
 2 files changed, 131 insertions(+), 2 deletions(-)
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/internal/util/ExceptionCollector.java

diff --git a/org.spearce.egit.core/META-INF/MANIFEST.MF b/org.spearce.egit.core/META-INF/MANIFEST.MF
index e13732b..20df15f 100644
--- a/org.spearce.egit.core/META-INF/MANIFEST.MF
+++ b/org.spearce.egit.core/META-INF/MANIFEST.MF
@@ -12,8 +12,9 @@ Require-Bundle: org.eclipse.core.runtime,
  org.spearce.jgit,
  org.eclipse.core.filesystem,
  org.eclipse.ui
-Export-Package: org.spearce.egit.core.internal.storage;x-friends:="org.spearce.egit.ui",
- org.spearce.egit.core,
+Export-Package: org.spearce.egit.core,
+ org.spearce.egit.core.internal.storage;x-friends:="org.spearce.egit.ui",
+ org.spearce.egit.core.internal.util;x-friends:="org.spearce.egit.ui",
  org.spearce.egit.core.op,
  org.spearce.egit.core.project
 Bundle-ActivationPolicy: lazy
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/internal/util/ExceptionCollector.java b/org.spearce.egit.core/src/org/spearce/egit/core/internal/util/ExceptionCollector.java
new file mode 100644
index 0000000..d99d651
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/internal/util/ExceptionCollector.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.spearce.egit.core.internal.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * Collects exceptions and can be configured to ignore duplicates exceptions.
+ * Exceptions can be logged and a MultiStatus containing all collected
+ * exceptions can be returned.
+ * 
+ * @see org.eclipse.core.runtime.MultiStatus
+ * @see org.eclipse.core.runtime.IStatus
+ * 
+ * @since 3.0
+ */
+public class ExceptionCollector {
+
+	private final List<IStatus> statuses = new ArrayList<IStatus>();
+
+	private final String message;
+
+	private final String pluginId;
+
+	private final int severity;
+
+	private final ILog log;
+
+	/**
+	 * Creates a collector and initializes the parameters for the top-level
+	 * exception that would be returned from <code>getStatus</code> is
+	 * exceptions are collected.
+	 * 
+	 * @param message
+	 *            a human-readable message, localized to the current locale
+	 * @param pluginId
+	 *            the unique identifier of the relevant plug-in
+	 * @param severity
+	 *            the severity; one of <code>OK</code>, <code>ERROR</code>,
+	 *            <code>INFO</code>, or <code>WARNING</code>
+	 * @param log
+	 *            the log to output the exceptions to, or <code>null</code> if
+	 *            exceptions should not be logged.
+	 */
+	public ExceptionCollector(String message, String pluginId, int severity,
+			ILog log) {
+		this.message = message;
+		this.pluginId = pluginId;
+		this.severity = severity;
+		this.log = log;
+	}
+
+	/**
+	 * Clears the exceptions collected.
+	 */
+	public void clear() {
+		statuses.clear();
+	}
+
+	/**
+	 * Returns a status that represents the exceptions collected. If the
+	 * collector is empty <code>IStatus.OK</code> is returned. Otherwise a
+	 * MultiStatus containing all collected exceptions is returned.
+	 * 
+	 * @return a multistatus containing the exceptions collected or IStatus.OK
+	 *         if the collector is empty.
+	 */
+	public IStatus getStatus() {
+		if (statuses.isEmpty()) {
+			return Status.OK_STATUS;
+		} else {
+			final MultiStatus multiStatus = new MultiStatus(pluginId, severity,
+					message, null);
+			final Iterator it = statuses.iterator();
+			while (it.hasNext()) {
+				final IStatus status = (IStatus) it.next();
+				multiStatus.merge(status);
+			}
+			return multiStatus;
+		}
+	}
+
+	/**
+	 * Add this exception to the collector. If a log was specified in the
+	 * constructor then the exception will be output to the log. You can
+	 * retreive exceptions using <code>getStatus</code>.
+	 * 
+	 * @param exception
+	 *            the exception to collect
+	 */
+	public void handleException(CoreException exception) {
+		if (log != null) {
+			log.log(new Status(severity, pluginId, 0, message, exception));
+		}
+
+		// Record each status individually to flatten the resulting multi-status
+		final IStatus exceptionStatus = exception.getStatus();
+
+		// Wrap the exception so the stack trace is not lost.
+		final IStatus status = new Status(exceptionStatus.getSeverity(),
+				exceptionStatus.getPlugin(), exceptionStatus.getCode(),
+				exceptionStatus.getMessage(), exception);
+
+		recordStatus(status);
+		for (IStatus childStatus : status.getChildren())
+			recordStatus(childStatus);
+	}
+
+	private void recordStatus(IStatus status) {
+		statuses.add(status);
+	}
+}
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 05/12] Add new class SWTUtils with helper-methods for creating controls
  2009-02-11 18:40       ` [EGIT PATCH v2 04/12] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
@ 2009-02-11 18:40         ` Tor Arne Vestbø
  2009-02-11 18:40           ` [EGIT PATCH v2 06/12] Implement basic customizable label decorations with preferences Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Copied verbatim from org.eclipse.team.internal.ui and documented

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../src/org/spearce/egit/ui/internal/SWTUtils.java |  595 ++++++++++++++++++++
 1 files changed, 595 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/SWTUtils.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/SWTUtils.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/SWTUtils.java
new file mode 100644
index 0000000..fe65bbb
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/SWTUtils.java
@@ -0,0 +1,595 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *	 IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.spearce.egit.ui.internal;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.ui.dialogs.PreferenceLinkArea;
+import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
+
+/**
+ * A collection of factory methods for creating common SWT controls
+ */
+public class SWTUtils {
+
+	/** */
+	public static final int MARGINS_DEFAULT = -1;
+
+	/** */
+	public static final int MARGINS_NONE = 0;
+
+	/** */
+	public static final int MARGINS_DIALOG = 1;
+
+	/**
+	 * Creates a preference link which will open in the specified container
+	 * 
+	 * @param container
+	 * @param parent
+	 * @param pageId
+	 * @param text
+	 * 
+	 * @return the created link
+	 */
+	public static PreferenceLinkArea createPreferenceLink(
+			IWorkbenchPreferenceContainer container, Composite parent,
+			String pageId, String text) {
+		final PreferenceLinkArea area = new PreferenceLinkArea(parent,
+				SWT.NONE, pageId, text, container, null);
+		return area;
+	}
+
+	/**
+	 * Creates a grid data with the specified metrics
+	 * 
+	 * @param width
+	 * @param height
+	 * @param hFill
+	 * @param vFill
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createGridData(int width, int height, boolean hFill,
+			boolean vFill) {
+		return createGridData(width, height, hFill ? SWT.FILL : SWT.BEGINNING,
+				vFill ? SWT.FILL : SWT.CENTER, hFill, vFill);
+	}
+
+	/**
+	 * Creates a grid data with the specified metrics
+	 * 
+	 * @param width
+	 * @param height
+	 * @param hAlign
+	 * @param vAlign
+	 * @param hGrab
+	 * @param vGrab
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createGridData(int width, int height, int hAlign,
+			int vAlign, boolean hGrab, boolean vGrab) {
+		final GridData gd = new GridData(hAlign, vAlign, hGrab, vGrab);
+		gd.widthHint = width;
+		gd.heightHint = height;
+		return gd;
+	}
+
+	/**
+	 * Creates a horizontal grid data with the default metrics
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createHFillGridData() {
+		return createHFillGridData(1);
+	}
+
+	/**
+	 * Creates a horizontal grid data with the specified span
+	 * 
+	 * @param span
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createHFillGridData(int span) {
+		final GridData gd = createGridData(0, SWT.DEFAULT, SWT.FILL,
+				SWT.CENTER, true, false);
+		gd.horizontalSpan = span;
+		return gd;
+	}
+
+	/**
+	 * Creates a horizontal fill composite with the specified margins
+	 * 
+	 * @param parent
+	 * @param margins
+	 * 
+	 * @return the created composite
+	 */
+	public static Composite createHFillComposite(Composite parent, int margins) {
+		return createHFillComposite(parent, margins, 1);
+	}
+
+	/**
+	 * Creates a horizontal fill composite with the specified margins and
+	 * columns
+	 * 
+	 * @param parent
+	 * @param margins
+	 * @param columns
+	 * 
+	 * @return the created composite
+	 */
+	public static Composite createHFillComposite(Composite parent, int margins,
+			int columns) {
+		final Composite composite = new Composite(parent, SWT.NONE);
+		composite.setFont(parent.getFont());
+		composite.setLayoutData(createHFillGridData());
+		composite.setLayout(createGridLayout(columns,
+				new PixelConverter(parent), margins));
+		return composite;
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill composite with the specified margins
+	 * 
+	 * @param parent
+	 * @param margins
+	 * 
+	 * @return the created composite
+	 */
+	public static Composite createHVFillComposite(Composite parent, int margins) {
+		return createHVFillComposite(parent, margins, 1);
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill composite with the specified margins
+	 * and columns
+	 * 
+	 * @param parent
+	 * @param margins
+	 * @param columns
+	 * 
+	 * @return the created composite
+	 */
+	public static Composite createHVFillComposite(Composite parent,
+			int margins, int columns) {
+		final Composite composite = new Composite(parent, SWT.NONE);
+		composite.setFont(parent.getFont());
+		composite.setLayoutData(createHVFillGridData());
+		composite.setLayout(createGridLayout(columns,
+				new PixelConverter(parent), margins));
+		return composite;
+	}
+
+	/**
+	 * Creates a horizontal fill group with the specified text and margins
+	 * 
+	 * @param parent
+	 * @param text
+	 * @param margins
+	 * @return the created group
+	 */
+	public static Group createHFillGroup(Composite parent, String text,
+			int margins) {
+		return createHFillGroup(parent, text, margins, 1);
+	}
+
+	/**
+	 * Creates a horizontal fill group with the specified text, margins and rows
+	 * 
+	 * @param parent
+	 * @param text
+	 * @param margins
+	 * @param rows
+	 * 
+	 * @return the created group
+	 */
+	public static Group createHFillGroup(Composite parent, String text,
+			int margins, int rows) {
+		final Group group = new Group(parent, SWT.NONE);
+		group.setFont(parent.getFont());
+		group.setLayoutData(createHFillGridData());
+		if (text != null)
+			group.setText(text);
+		group.setLayout(createGridLayout(rows, new PixelConverter(parent),
+				margins));
+		return group;
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill group with the specified text and
+	 * margins
+	 * 
+	 * @param parent
+	 * @param text
+	 * @param margins
+	 * 
+	 * @return the created group
+	 */
+	public static Group createHVFillGroup(Composite parent, String text,
+			int margins) {
+		return createHVFillGroup(parent, text, margins, 1);
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill group with the specified text, margins
+	 * and rows
+	 * 
+	 * @param parent
+	 * @param text
+	 * @param margins
+	 * @param rows
+	 * 
+	 * @return the created group
+	 */
+	public static Group createHVFillGroup(Composite parent, String text,
+			int margins, int rows) {
+		final Group group = new Group(parent, SWT.NONE);
+		group.setFont(parent.getFont());
+		group.setLayoutData(createHVFillGridData());
+		if (text != null)
+			group.setText(text);
+		group.setLayout(createGridLayout(rows, new PixelConverter(parent),
+				margins));
+		return group;
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill grid data with the default metrics
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createHVFillGridData() {
+		return createHVFillGridData(1);
+	}
+
+	/**
+	 * Creates a horizontal/vertical fill grid data with the specified span
+	 * 
+	 * @param span
+	 * 
+	 * @return the created grid data
+	 */
+	public static GridData createHVFillGridData(int span) {
+		final GridData gd = createGridData(0, 0, true, true);
+		gd.horizontalSpan = span;
+		return gd;
+	}
+
+	/**
+	 * Creates a grid layout with the specified number of columns and the
+	 * standard spacings.
+	 * 
+	 * @param numColumns
+	 *            the number of columns
+	 * @param converter
+	 *            the pixel converter
+	 * @param margins
+	 *            one of <code>MARGINS_DEFAULT</code>, <code>MARGINS_NONE</code>
+	 *            or <code>MARGINS_DIALOG</code>.
+	 * 
+	 * @return the created grid layout
+	 */
+	public static GridLayout createGridLayout(int numColumns,
+			PixelConverter converter, int margins) {
+		Assert.isTrue(margins == MARGINS_DEFAULT || margins == MARGINS_NONE
+				|| margins == MARGINS_DIALOG);
+
+		final GridLayout layout = new GridLayout(numColumns, false);
+		layout.horizontalSpacing = converter
+				.convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
+		layout.verticalSpacing = converter
+				.convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
+
+		switch (margins) {
+		case MARGINS_NONE:
+			layout.marginLeft = layout.marginRight = 0;
+			layout.marginTop = layout.marginBottom = 0;
+			break;
+		case MARGINS_DIALOG:
+			layout.marginLeft = layout.marginRight = converter
+					.convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
+			layout.marginTop = layout.marginBottom = converter
+					.convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
+			break;
+		case MARGINS_DEFAULT:
+			layout.marginLeft = layout.marginRight = layout.marginWidth;
+			layout.marginTop = layout.marginBottom = layout.marginHeight;
+		}
+		layout.marginWidth = layout.marginHeight = 0;
+		return layout;
+	}
+
+	/**
+	 * Creates a label with the specified message
+	 * 
+	 * @param parent
+	 * @param message
+	 * 
+	 * @return the created label
+	 */
+	public static Label createLabel(Composite parent, String message) {
+		return createLabel(parent, message, 1);
+	}
+
+	/**
+	 * Creates a label with the specified message and span
+	 * 
+	 * @param parent
+	 * @param message
+	 * @param span
+	 * 
+	 * @return the created label
+	 */
+	public static Label createLabel(Composite parent, String message, int span) {
+		final Label label = new Label(parent, SWT.WRAP);
+		if (message != null)
+			label.setText(message);
+		label.setLayoutData(createHFillGridData(span));
+		return label;
+	}
+
+	/**
+	 * Creates a check box with the specified message
+	 * 
+	 * @param parent
+	 * @param message
+	 * 
+	 * @return the created check box
+	 */
+	public static Button createCheckBox(Composite parent, String message) {
+		return createCheckBox(parent, message, 1);
+	}
+
+	/**
+	 * Creates a check box with the specified message and span
+	 * 
+	 * @param parent
+	 * @param message
+	 * @param span
+	 * 
+	 * @return the created check box
+	 */
+	public static Button createCheckBox(Composite parent, String message,
+			int span) {
+		final Button button = new Button(parent, SWT.CHECK);
+		button.setText(message);
+		button.setLayoutData(createHFillGridData(span));
+		return button;
+	}
+
+	/**
+	 * Creates a radio button with the specified message
+	 * 
+	 * @param parent
+	 * @param message
+	 * 
+	 * @return the created radio button
+	 */
+	public static Button createRadioButton(Composite parent, String message) {
+		return createRadioButton(parent, message, 1);
+	}
+
+	/**
+	 * Creates a radio button with the specified message and span
+	 * 
+	 * @param parent
+	 * @param message
+	 * @param span
+	 * 
+	 * @return the created radio button
+	 */
+	public static Button createRadioButton(Composite parent, String message,
+			int span) {
+		final Button button = new Button(parent, SWT.RADIO);
+		button.setText(message);
+		button.setLayoutData(createHFillGridData(span));
+		return button;
+	}
+
+	/**
+	 * Creates a text control
+	 * 
+	 * @param parent
+	 * 
+	 * @return the created text control
+	 */
+	public static Text createText(Composite parent) {
+		return createText(parent, 1);
+	}
+
+	/**
+	 * Creates a text control with the specified span
+	 * 
+	 * @param parent
+	 * @param span
+	 * 
+	 * @return the created text control
+	 */
+	public static Text createText(Composite parent, int span) {
+		final Text text = new Text(parent, SWT.SINGLE | SWT.BORDER);
+		text.setLayoutData(createHFillGridData(span));
+		return text;
+	}
+
+	/**
+	 * Creates a place holder with the specified height and span
+	 * 
+	 * @param parent
+	 * @param heightInChars
+	 * @param span
+	 * 
+	 * @return the created place holder
+	 */
+	public static Control createPlaceholder(Composite parent,
+			int heightInChars, int span) {
+		Assert.isTrue(heightInChars > 0);
+		final Control placeHolder = new Composite(parent, SWT.NONE);
+		final GridData gd = new GridData(SWT.BEGINNING, SWT.TOP, false, false);
+		gd.heightHint = new PixelConverter(parent)
+				.convertHeightInCharsToPixels(heightInChars);
+		gd.horizontalSpan = span;
+		placeHolder.setLayoutData(gd);
+		return placeHolder;
+	}
+
+	/**
+	 * Creates a place holder with the specified height
+	 * 
+	 * @param parent
+	 * @param heightInChars
+	 * @return the created place holder
+	 */
+	public static Control createPlaceholder(Composite parent, int heightInChars) {
+		return createPlaceholder(parent, heightInChars, 1);
+	}
+
+	/**
+	 * Creates a pixel converter
+	 * 
+	 * @param control
+	 * 
+	 * @return the created pixel converter
+	 */
+	public static PixelConverter createDialogPixelConverter(Control control) {
+		Dialog.applyDialogFont(control);
+		return new PixelConverter(control);
+	}
+
+	/**
+	 * Calculates the size of the specified controls, using the specified
+	 * converter
+	 * 
+	 * @param converter
+	 * @param controls
+	 * 
+	 * @return the size of the control(s)
+	 */
+	public static int calculateControlSize(PixelConverter converter,
+			Control[] controls) {
+		return calculateControlSize(converter, controls, 0, controls.length - 1);
+	}
+
+	/**
+	 * Calculates the size of the specified subset of controls, using the
+	 * specified converter
+	 * 
+	 * @param converter
+	 * @param controls
+	 * @param start
+	 * @param end
+	 * 
+	 * @return the created control
+	 */
+	public static int calculateControlSize(PixelConverter converter,
+			Control[] controls, int start, int end) {
+		int minimum = converter
+				.convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
+		for (int i = start; i <= end; i++) {
+			final int length = controls[i]
+					.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+			if (minimum < length)
+				minimum = length;
+		}
+		return minimum;
+	}
+
+	/**
+	 * Equalizes the specified controls using the specified converter
+	 * 
+	 * @param converter
+	 * @param controls
+	 */
+	public static void equalizeControls(PixelConverter converter,
+			Control[] controls) {
+		equalizeControls(converter, controls, 0, controls.length - 1);
+	}
+
+	/**
+	 * Equalizes the specified subset of controls using the specified converter
+	 * 
+	 * @param converter
+	 * @param controls
+	 * @param start
+	 * @param end
+	 */
+	public static void equalizeControls(PixelConverter converter,
+			Control[] controls, int start, int end) {
+		final int size = calculateControlSize(converter, controls, start, end);
+		for (int i = start; i <= end; i++) {
+			final Control button = controls[i];
+			if (button.getLayoutData() instanceof GridData) {
+				((GridData) button.getLayoutData()).widthHint = size;
+			}
+		}
+	}
+
+	/**
+	 * Gets the width of the longest string in <code>strings</code>, using the
+	 * specified pixel converter
+	 * 
+	 * @param converter
+	 * @param strings
+	 * 
+	 * @return the width of the longest string
+	 */
+	public static int getWidthInCharsForLongest(PixelConverter converter,
+			String[] strings) {
+		int minimum = 0;
+		for (int i = 0; i < strings.length; i++) {
+			final int length = converter.convertWidthInCharsToPixels(strings[i]
+					.length());
+			if (minimum < length)
+				minimum = length;
+		}
+		return minimum;
+	}
+
+	private static class PixelConverter {
+
+		private final FontMetrics fFontMetrics;
+
+		public PixelConverter(Control control) {
+			GC gc = new GC(control);
+			try {
+				gc.setFont(control.getFont());
+				fFontMetrics = gc.getFontMetrics();
+			} finally {
+				gc.dispose();
+			}
+		}
+
+		public int convertHeightInCharsToPixels(int chars) {
+			return Dialog.convertHeightInCharsToPixels(fFontMetrics, chars);
+		}
+
+		public int convertHorizontalDLUsToPixels(int dlus) {
+			return Dialog.convertHorizontalDLUsToPixels(fFontMetrics, dlus);
+		}
+
+		public int convertVerticalDLUsToPixels(int dlus) {
+			return Dialog.convertVerticalDLUsToPixels(fFontMetrics, dlus);
+		}
+
+		public int convertWidthInCharsToPixels(int chars) {
+			return Dialog.convertWidthInCharsToPixels(fFontMetrics, chars);
+		}
+	}
+}
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 06/12] Implement basic customizable label decorations with preferences
  2009-02-11 18:40         ` [EGIT PATCH v2 05/12] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
@ 2009-02-11 18:40           ` Tor Arne Vestbø
  2009-02-11 18:40             ` [EGIT PATCH v2 07/12] Add binding for name of the current branch Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Currently the only binding available is the resource name, but
this commit enables a framework for adding more bindings.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.ui/plugin.properties              |    1 +
 org.spearce.egit.ui/plugin.xml                     |   12 +-
 .../src/org/spearce/egit/ui/Activator.java         |   16 +
 .../egit/ui/PluginPreferenceInitializer.java       |    8 +
 .../src/org/spearce/egit/ui/UIPreferences.java     |    9 +
 .../src/org/spearce/egit/ui/UIText.java            |   63 ++-
 .../egit/ui/internal/actions/BranchAction.java     |    4 +-
 .../egit/ui/internal/actions/Disconnect.java       |    4 +-
 .../egit/ui/internal/actions/ResetAction.java      |    4 +-
 .../decorators/GitLightweightDecorator.java        |  538 +++++++++++++++
 .../internal/decorators/GitResourceDecorator.java  |  454 -------------
 .../internal/decorators/IDecoratableResource.java  |   31 +
 .../preferences/GitDecoratorPreferencePage.java    |  702 ++++++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   25 +-
 14 files changed, 1405 insertions(+), 466 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
 delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java

diff --git a/org.spearce.egit.ui/plugin.properties b/org.spearce.egit.ui/plugin.properties
index fa043f1..58b879f 100644
--- a/org.spearce.egit.ui/plugin.properties
+++ b/org.spearce.egit.ui/plugin.properties
@@ -64,3 +64,4 @@ Theme_CommitMessageFont_description=This font is used to show a commit message.
 GitPreferences_name=Git
 GitPreferences_HistoryPreferencePage_name=History
 GitPreferences_WindowCachePreferencePage_name=Window Cache
+GitPreferences_DecoratorPreferencePage_name=Label Decorations
diff --git a/org.spearce.egit.ui/plugin.xml b/org.spearce.egit.ui/plugin.xml
index 869108c..2f23559 100644
--- a/org.spearce.egit.ui/plugin.xml
+++ b/org.spearce.egit.ui/plugin.xml
@@ -200,6 +200,14 @@
 		  id="org.spearce.egit.ui.keyword.git">
 	    </keywordReference>
       </page>
+	  <page name="%GitPreferences_DecoratorPreferencePage_name"
+	    category="org.spearce.egit.ui.GitPreferences"
+            class="org.spearce.egit.ui.internal.preferences.GitDecoratorPreferencePage"
+	    id="org.spearce.egit.ui.internal.preferences.GitDecoratorPreferencePage" >
+	    <keywordReference
+		  id="org.spearce.egit.ui.keyword.git">
+	    </keywordReference>
+      </page>
    </extension>
 
    <extension point="org.eclipse.ui.propertyPages">
@@ -233,10 +241,10 @@
             lightweight="true"
             adaptable="true"
             label="%Decorator_name"
-            class="org.spearce.egit.ui.internal.decorators.GitResourceDecorator"
+            class="org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"
             state="true"
             location="BOTTOM_RIGHT"
-            id="org.spearce.egit.ui.internal.decorators.GitResourceDecorator">
+            id="org.spearce.egit.ui.internal.decorators.GitLightweightDecorator">
             <enablement>
               <objectClass name="org.eclipse.core.resources.IResource"/>
             </enablement>
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
index 534c408..87f45d2 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java
@@ -33,6 +33,7 @@
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jsch.core.IJSchService;
 import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.eclipse.ui.themes.ITheme;
 import org.osgi.framework.BundleContext;
@@ -80,6 +81,21 @@ public static String getPluginId() {
 	}
 
 	/**
+	 * Returns the standard display to be used. The method first checks, if the
+	 * thread calling this method has an associated display. If so, this display
+	 * is returned. Otherwise the method returns the default display.
+	 * 
+	 * @return the display to use
+	 */
+	public static Display getStandardDisplay() {
+		Display display = Display.getCurrent();
+		if (display == null) {
+			display = Display.getDefault();
+		}
+		return display;
+	}
+
+	/**
 	 * Instantiate an error exception.
 	 * 
 	 * @param message
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
index bb7381f..79c2665 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
@@ -35,6 +35,14 @@ public void initializeDefaultPreferences() {
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, true);
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_TOOLTIPS, false);
 
+		prefs.setDefault(UIPreferences.DECORATOR_FILETEXT_DECORATION,
+				UIText.DecoratorPreferencesPage_fileFormatDefault);
+		prefs.setDefault(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
+				UIText.DecoratorPreferencesPage_folderFormatDefault);
+		prefs.setDefault(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
+				UIText.DecoratorPreferencesPage_projectFormatDefault);
+		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
+
 		w = new int[] { 500, 500 };
 		UIPreferences.setDefault(prefs,
 				UIPreferences.RESOURCEHISTORY_GRAPH_SPLIT, w);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
index 5ab6b25..a6168a0 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
@@ -52,6 +52,15 @@
 	/** */
 	public final static String THEME_CommitMessageFont = "org.spearce.egit.ui.CommitMessageFont";
 
+	/** */
+	public final static String DECORATOR_CALCULATE_DIRTY = "decorator_calculate_dirty";
+	/** */
+	public final static String DECORATOR_FILETEXT_DECORATION = "decorator_filetext_decoration";
+	/** */
+	public final static String DECORATOR_FOLDERTEXT_DECORATION = "decorator_foldertext_decoration";
+	/** */
+	public final static String DECORATOR_PROJECTTEXT_DECORATION = "decorator_projecttext_decoration";
+
 	/**
 	 * Get the preference values associated with a fixed integer array.
 	 * 
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 249f2a0..a7ef408 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -446,9 +446,6 @@
 	public static String RefSpecPage_annotatedTagsNoTags;
 
 	/** */
-	public static String Decorator_failedLazyLoading;
-
-	/** */
 	public static String QuickDiff_failedLoading;
 
 	/** */
@@ -928,6 +925,66 @@
 	/** */
 	public static String HistoryPage_ShowAllVersionsForFolder;
 
+	/** */
+	public static String Decorator_exceptionMessage;
+
+	/** */
+	public static String DecoratorPreferencesPage_addVariablesTitle;
+
+	/** */
+	public static String DecoratorPreferencesPage_addVariablesAction;
+
+	/** */
+	public static String DecoratorPreferencesPage_computeDeep;
+
+	/** */
+	public static String DecoratorPreferencesPage_description;
+
+	/** */
+	public static String DecoratorPreferencesPage_decorationSettings;
+
+	/** */
+	public static String DecoratorPreferencesPage_preview;
+
+	/** */
+	public static String DecoratorPreferencesPage_fileFormatLabel;
+
+	/** */
+	public static String DecoratorPreferencesPage_folderFormatLabel;
+
+	/** */
+	public static String DecoratorPreferencesPage_projectFormatLabel;
+
+	/** */
+	public static String DecoratorPreferencesPage_fileFormatDefault;
+
+	/** */
+	public static String DecoratorPreferencesPage_projectFormatDefault;
+
+	/** */
+	public static String DecoratorPreferencesPage_folderFormatDefault;
+
+	/** */
+	public static String DecoratorPreferencesPage_generalTabFolder;
+
+	/** */
+	public static String DecoratorPreferencesPage_nameResourceVariable;
+
+	/** */
+	public static String DecoratorPreferencesPage_selectFormats;
+
+	/** */
+	public static String DecoratorPreferencesPage_selectVariablesToAdd;
+
+	/** */
+	public static String DecoratorPreferencesPage_textLabel;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconLabel;
+
+	/** */
+	public static String DecoratorPreferencesPage_labelDecorationsLink;
+
 	static {
 		initializeMessages(UIText.class.getPackage().getName() + ".uitext",
 				UIText.class);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java
index 7ca4d10..38ee3d8 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java
@@ -19,7 +19,7 @@
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.swt.widgets.Display;
 import org.spearce.egit.core.op.BranchOperation;
-import org.spearce.egit.ui.internal.decorators.GitResourceDecorator;
+import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator;
 import org.spearce.egit.ui.internal.dialogs.BranchSelectionDialog;
 import org.spearce.jgit.lib.Repository;
 
@@ -56,7 +56,7 @@ public void run(final IProgressMonitor monitor)
 				throws InvocationTargetException {
 					try {
 						new BranchOperation(repository, refName).run(monitor);
-						GitResourceDecorator.refresh();
+						GitLightweightDecorator.refresh();
 					} catch (final CoreException ce) {
 						ce.printStackTrace();
 						Display.getDefault().asyncExec(new Runnable() {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java
index 18d6b4b..4201822 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java
@@ -13,7 +13,7 @@
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.jface.action.IAction;
 import org.spearce.egit.core.op.DisconnectProviderOperation;
-import org.spearce.egit.ui.internal.decorators.GitResourceDecorator;
+import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator;
 
 /**
  *	Action to disassociate a project from its Git repository.
@@ -27,6 +27,6 @@ protected IWorkspaceRunnable createOperation(final IAction act,
 	}
 
 	protected void postOperation() {
-		GitResourceDecorator.refresh();
+		GitLightweightDecorator.refresh();
 	}
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java
index b05cdd3..a329925 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java
@@ -19,7 +19,7 @@
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.spearce.egit.core.op.ResetOperation;
 import org.spearce.egit.core.op.ResetOperation.ResetType;
-import org.spearce.egit.ui.internal.decorators.GitResourceDecorator;
+import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator;
 import org.spearce.egit.ui.internal.dialogs.BranchSelectionDialog;
 import org.spearce.jgit.lib.Repository;
 
@@ -55,7 +55,7 @@ public void run(final IProgressMonitor monitor)
 					throws InvocationTargetException {
 						try {
 							new ResetOperation(repository, refName, type).run(monitor);
-							GitResourceDecorator.refresh();
+							GitLightweightDecorator.refresh();
 						} catch (CoreException ce) {
 							ce.printStackTrace();
 							throw new InvocationTargetException(ce);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
new file mode 100644
index 0000000..85b9173
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -0,0 +1,538 @@
+/*******************************************************************************
+ * Copyright (C) 2007, IBM Corporation and others
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.ui.internal.decorators;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.mapping.ResourceMapping;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;
+import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.team.ui.TeamUI;
+import org.eclipse.ui.IContributorResourceAdapter;
+import org.eclipse.ui.PlatformUI;
+import org.spearce.egit.core.internal.util.ExceptionCollector;
+import org.spearce.egit.core.project.GitProjectData;
+import org.spearce.egit.core.project.RepositoryChangeListener;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIPreferences;
+import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.RefsChangedEvent;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryChangedEvent;
+import org.spearce.jgit.lib.RepositoryListener;
+
+/**
+ * Supplies annotations for displayed resources
+ * 
+ * This decorator provides annotations to indicate the status of each resource
+ * when compared to <code>HEAD</code>, as well as the index in the relevant
+ * repository.
+ * 
+ * TODO: Add support for colors and font decoration
+ */
+public class GitLightweightDecorator extends LabelProvider implements
+		ILightweightLabelDecorator, IPropertyChangeListener,
+		IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
+
+	/**
+	 * Property constant pointing back to the extension point id of the
+	 * decorator
+	 */
+	public static final String DECORATOR_ID = "org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"; //$NON-NLS-1$
+
+	/**
+	 * Bit-mask describing interesting changes for IResourceChangeListener
+	 * events
+	 */
+	private static int INTERESTING_CHANGES = IResourceDelta.CONTENT
+			| IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO
+			| IResourceDelta.OPEN | IResourceDelta.REPLACED
+			| IResourceDelta.TYPE;
+
+	/**
+	 * Collector for keeping the error view from filling up with exceptions
+	 */
+	private static ExceptionCollector exceptions = new ExceptionCollector(
+			UIText.Decorator_exceptionMessage, Activator.getPluginId(),
+			IStatus.ERROR, Activator.getDefault().getLog());
+
+	/**
+	 * Constructs a new Git resource decorator
+	 */
+	public GitLightweightDecorator() {
+		TeamUI.addPropertyChangeListener(this);
+		Activator.addPropertyChangeListener(this);
+		PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
+				.addPropertyChangeListener(this);
+		Repository.addAnyRepositoryChangedListener(this);
+		GitProjectData.addRepositoryChangeListener(this);
+		ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
+				IResourceChangeEvent.POST_CHANGE);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
+	 */
+	@Override
+	public void dispose() {
+		super.dispose();
+		PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
+				.removePropertyChangeListener(this);
+		TeamUI.removePropertyChangeListener(this);
+		Activator.removePropertyChangeListener(this);
+		Repository.removeAnyRepositoryChangedListener(this);
+		GitProjectData.removeRepositoryChangeListener(this);
+		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+	}
+
+	/**
+	 * This method should only be called by the decorator thread.
+	 * 
+	 * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object,
+	 *      org.eclipse.jface.viewers.IDecoration)
+	 */
+	public void decorate(Object element, IDecoration decoration) {
+		final IResource resource = getResource(element);
+		if (resource == null)
+			return;
+
+		// Don't decorate the workspace root
+		if (resource.getType() == IResource.ROOT)
+			return;
+
+		// Don't decorate non-existing resources
+		if (!resource.exists() && !resource.isPhantom())
+			return;
+
+		// Make sure we're dealing with a Git project
+		final RepositoryMapping mapping = RepositoryMapping
+				.getMapping(resource);
+		if (mapping == null)
+			return;
+
+		// Cannot decorate linked resources
+		if (mapping.getRepoRelativePath(resource) == null)
+			return;
+
+		// Don't decorate if UI plugin is not running
+		Activator activator = Activator.getDefault();
+		if (activator == null)
+			return;
+
+		DecorationHelper helper = new DecorationHelper(activator
+				.getPreferenceStore());
+		helper.decorate(decoration, new DecoratableResourceAdapter(resource));
+	}
+
+	private class DecoratableResourceAdapter implements IDecoratableResource {
+
+		private IResource resource;
+
+		public DecoratableResourceAdapter(IResource resourceToWrap) {
+			resource = resourceToWrap;
+		}
+
+		public String getName() {
+			return resource.getName();
+		}
+
+		public int getType() {
+			return resource.getType();
+		}
+	}
+
+	/**
+	 * Helper class for doing resource decoration, based on the given
+	 * preferences
+	 * 
+	 * Used for real-time decoration, as well as in the decorator preview
+	 * preferences page
+	 */
+	public static class DecorationHelper {
+
+		private IPreferenceStore store;
+
+		/** */
+		public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
+
+		/**
+		 * Constructs a decorator using the rules from the given
+		 * <code>preferencesStore</code>
+		 * 
+		 * @param preferencesStore
+		 *            the preferences store with the preferred decorator rules
+		 */
+		public DecorationHelper(IPreferenceStore preferencesStore) {
+			store = preferencesStore;
+		}
+
+		/**
+		 * Decorates the given <code>decoration</code> based on the state of the
+		 * given <code>resource</code>, using the preferences passed when
+		 * constructing this decoration helper.
+		 * 
+		 * @param decoration
+		 *            the decoration to decorate
+		 * @param resource
+		 *            the resource to retrieve state from
+		 */
+		public void decorate(IDecoration decoration,
+				IDecoratableResource resource) {
+			String format = "";
+			switch (resource.getType()) {
+			case IResource.FILE:
+				format = store
+						.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION);
+				break;
+			case IResource.FOLDER:
+				format = store
+						.getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION);
+				break;
+			case IResource.PROJECT:
+				format = store
+						.getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION);
+				break;
+			}
+
+			Map<String, String> bindings = new HashMap<String, String>();
+			bindings.put(BINDING_RESOURCE_NAME, resource.getName());
+
+			decorate(decoration, format, bindings);
+		}
+
+		/**
+		 * Decorates the given <code>decoration</code>, using the given
+		 * <code>format</code>, and mapped using <code>bindings</code>
+		 * 
+		 * @param decoration
+		 *            the decoration to decorate
+		 * @param format
+		 *            the format to base the decoration on
+		 * @param bindings
+		 *            the bindings between variables in the format and actual
+		 *            values
+		 */
+		public static void decorate(IDecoration decoration, String format,
+				Map bindings) {
+			StringBuffer prefix = new StringBuffer();
+			StringBuffer suffix = new StringBuffer();
+			StringBuffer output = prefix;
+
+			int length = format.length();
+			int start = -1;
+			int end = length;
+			while (true) {
+				if ((end = format.indexOf('{', start)) > -1) {
+					output.append(format.substring(start + 1, end));
+					if ((start = format.indexOf('}', end)) > -1) {
+						String key = format.substring(end + 1, start);
+						String s;
+
+						// We use the BINDING_RESOURCE_NAME key to determine if
+						// we are doing the prefix or suffix. The name isn't
+						// actually part of either.
+						if (key.equals(BINDING_RESOURCE_NAME)) {
+							output = suffix;
+							s = null;
+						} else {
+							s = (String) bindings.get(key);
+						}
+
+						if (s != null) {
+							output.append(s);
+						} else {
+							// Support removing prefix character if binding is
+							// null
+							int curLength = output.length();
+							if (curLength > 0) {
+								char c = output.charAt(curLength - 1);
+								if (c == ':' || c == '@') {
+									output.deleteCharAt(curLength - 1);
+								}
+							}
+						}
+					} else {
+						output.append(format.substring(end, length));
+						break;
+					}
+				} else {
+					output.append(format.substring(start + 1, length));
+					break;
+				}
+			}
+
+			String prefixString = prefix.toString().replaceAll("^\\s+", "");
+			if (prefixString != null) {
+				decoration.addPrefix(TextProcessor.process(prefixString,
+						"()[].")); //$NON-NLS-1$
+			}
+			String suffixString = suffix.toString().replaceAll("\\s+$", "");
+			if (suffixString != null) {
+				decoration.addSuffix(TextProcessor.process(suffixString,
+						"()[].")); //$NON-NLS-1$
+			}
+		}
+	}
+
+	// -------- Refresh handling --------
+
+	/**
+	 * Perform a blanket refresh of all decorations
+	 */
+	public static void refresh() {
+		Display.getDefault().asyncExec(new Runnable() {
+			public void run() {
+				Activator.getDefault().getWorkbench().getDecoratorManager()
+						.update(DECORATOR_ID);
+			}
+		});
+	}
+
+	/**
+	 * Callback for IPropertyChangeListener events
+	 * 
+	 * If any of the relevant preferences has been changed we refresh all
+	 * decorations (all projects and their resources).
+	 * 
+	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
+	 */
+	public void propertyChange(PropertyChangeEvent event) {
+		final String prop = event.getProperty();
+		// If the property is of any interest to us
+		if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED)
+				|| prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED)
+				|| prop.equals(Activator.DECORATORS_CHANGED)) {
+			postLabelEvent(new LabelProviderChangedEvent(this, null /* all */));
+		}
+	}
+
+	/**
+	 * Callback for IResourceChangeListener events
+	 * 
+	 * Schedules a refresh of the changed resource
+	 * 
+	 * If the preference for computing deep dirty states has been set we walk
+	 * the ancestor tree of the changed resource and update all parents as well.
+	 * 
+	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
+	 */
+	public void resourceChanged(IResourceChangeEvent event) {
+		final Set<IResource> resourcesToUpdate = new HashSet<IResource>();
+
+		try { // Compute the changed resources by looking at the delta
+			event.getDelta().accept(new IResourceDeltaVisitor() {
+				public boolean visit(IResourceDelta delta) throws CoreException {
+					final IResource resource = delta.getResource();
+
+					if (resource.getType() == IResource.ROOT) {
+						// Continue with the delta
+						return true;
+					}
+
+					if (resource.getType() == IResource.PROJECT) {
+						// If the project is not accessible, don't process it
+						if (!resource.isAccessible())
+							return false;
+					}
+
+					// If the file has changed but not in a way that we care
+					// about
+					// (e.g. marker changes to files) then ignore the change
+					if (delta.getKind() == IResourceDelta.CHANGED
+							&& (delta.getFlags() & INTERESTING_CHANGES) == 0) {
+						return true;
+					}
+
+					// All seems good, schedule the resource for update
+					resourcesToUpdate.add(resource);
+					return true;
+				}
+			}, true /* includePhantoms */);
+		} catch (final CoreException e) {
+			handleException(null, e);
+		}
+
+		// If deep decorator calculation is enabled in the preferences we
+		// walk the ancestor tree of each of the changed resources and add
+		// their parents to the update set
+		final IPreferenceStore store = Activator.getDefault()
+				.getPreferenceStore();
+		if (store.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)) {
+			final IResource[] changedResources = resourcesToUpdate
+					.toArray(new IResource[resourcesToUpdate.size()]);
+			for (int i = 0; i < changedResources.length; i++) {
+				IResource current = changedResources[i];
+				while (current.getType() != IResource.ROOT) {
+					current = current.getParent();
+					resourcesToUpdate.add(current);
+				}
+			}
+		}
+
+		postLabelEvent(new LabelProviderChangedEvent(this, resourcesToUpdate
+				.toArray()));
+	}
+
+	/**
+	 * Callback for RepositoryListener events
+	 * 
+	 * We resolve the repository mapping for the changed repository and forward
+	 * that to repositoryChanged(RepositoryMapping).
+	 * 
+	 * @param e
+	 *            The original change event
+	 */
+	private void repositoryChanged(RepositoryChangedEvent e) {
+		final Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
+		for (final IProject p : ResourcesPlugin.getWorkspace().getRoot()
+				.getProjects()) {
+			final RepositoryMapping mapping = RepositoryMapping.getMapping(p);
+			if (mapping != null && mapping.getRepository() == e.getRepository())
+				ms.add(mapping);
+		}
+		for (final RepositoryMapping m : ms) {
+			repositoryChanged(m);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.spearce.jgit.lib.RepositoryListener#indexChanged(org.spearce.jgit
+	 * .lib.IndexChangedEvent)
+	 */
+	public void indexChanged(IndexChangedEvent e) {
+		repositoryChanged(e);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.spearce.jgit.lib.RepositoryListener#refsChanged(org.spearce.jgit.
+	 * lib.RefsChangedEvent)
+	 */
+	public void refsChanged(RefsChangedEvent e) {
+		repositoryChanged(e);
+	}
+
+	/**
+	 * Callback for RepositoryChangeListener events, as well as
+	 * RepositoryListener events via repositoryChanged()
+	 * 
+	 * We resolve the project and schedule a refresh of each resource in the
+	 * project.
+	 * 
+	 * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping)
+	 */
+	public void repositoryChanged(RepositoryMapping mapping) {
+		final IProject project = mapping.getContainer().getProject();
+		if (project == null)
+			return;
+
+		final List<IResource> resources = new ArrayList<IResource>();
+		try {
+			project.accept(new IResourceVisitor() {
+				public boolean visit(IResource resource) {
+					resources.add(resource);
+					return true;
+				}
+			});
+			postLabelEvent(new LabelProviderChangedEvent(this, resources
+					.toArray()));
+		} catch (final CoreException e) {
+			handleException(project, e);
+		}
+	}
+
+	// -------- Helper methods --------
+
+	private static IResource getResource(Object element) {
+		if (element instanceof ResourceMapping) {
+			element = ((ResourceMapping) element).getModelObject();
+		}
+
+		IResource resource = null;
+		if (element instanceof IResource) {
+			resource = (IResource) element;
+		} else if (element instanceof IAdaptable) {
+			final IAdaptable adaptable = (IAdaptable) element;
+			resource = (IResource) adaptable.getAdapter(IResource.class);
+			if (resource == null) {
+				final IContributorResourceAdapter adapter = (IContributorResourceAdapter) adaptable
+						.getAdapter(IContributorResourceAdapter.class);
+				if (adapter != null)
+					resource = adapter.getAdaptedResource(adaptable);
+			}
+		}
+
+		return resource;
+	}
+
+	/**
+	 * Post the label event to the UI thread
+	 * 
+	 * @param event
+	 *            The event to post
+	 */
+	private void postLabelEvent(final LabelProviderChangedEvent event) {
+		Display.getDefault().asyncExec(new Runnable() {
+			public void run() {
+				fireLabelProviderChanged(event);
+			}
+		});
+	}
+
+	/**
+	 * Handle exceptions that occur in the decorator. Exceptions are only logged
+	 * for resources that are accessible (i.e. exist in an open project).
+	 * 
+	 * @param resource
+	 *            The resource that triggered the exception
+	 * @param e
+	 *            The exception that occurred
+	 */
+	private static void handleException(IResource resource, CoreException e) {
+		if (resource == null || resource.isAccessible())
+			exceptions.handleException(e);
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java
deleted file mode 100644
index f24b1eb..0000000
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2008, Google Inc.
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * See LICENSE for the full license text, also available.
- *******************************************************************************/
-package org.spearce.egit.ui.internal.decorators;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import org.eclipse.core.resources.IContainer;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceChangeEvent;
-import org.eclipse.core.resources.IResourceChangeListener;
-import org.eclipse.core.resources.IResourceDelta;
-import org.eclipse.core.resources.IResourceDeltaVisitor;
-import org.eclipse.core.resources.IResourceVisitor;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IAdaptable;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.ISchedulingRule;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.jface.viewers.IDecoration;
-import org.eclipse.jface.viewers.ILightweightLabelDecorator;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.viewers.LabelProviderChangedEvent;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.team.core.Team;
-import org.eclipse.ui.IDecoratorManager;
-import org.spearce.egit.core.project.GitProjectData;
-import org.spearce.egit.core.project.RepositoryChangeListener;
-import org.spearce.egit.core.project.RepositoryMapping;
-import org.spearce.egit.ui.Activator;
-import org.spearce.egit.ui.UIIcons;
-import org.spearce.egit.ui.UIText;
-import org.spearce.jgit.lib.Constants;
-import org.spearce.jgit.lib.GitIndex;
-import org.spearce.jgit.lib.IndexChangedEvent;
-import org.spearce.jgit.lib.RefsChangedEvent;
-import org.spearce.jgit.lib.Repository;
-import org.spearce.jgit.lib.RepositoryChangedEvent;
-import org.spearce.jgit.lib.RepositoryListener;
-import org.spearce.jgit.lib.RepositoryState;
-import org.spearce.jgit.lib.Tree;
-import org.spearce.jgit.lib.TreeEntry;
-import org.spearce.jgit.lib.GitIndex.Entry;
-
-/**
- * Supplies annotations for displayed resources.
- * <p>
- * This decorator provides annotations to indicate the status of each resource
- * when compared to <code>HEAD</code> as well as the index in the relevant
- * repository.
- * 
- * When either the index or the working directory is different from HEAD an
- * indicator is set.
- * 
- * </p>
- */
-public class GitResourceDecorator extends LabelProvider implements
-		ILightweightLabelDecorator {
-
-	static final String decoratorId = "org.spearce.egit.ui.internal.decorators.GitResourceDecorator";
-	static class ResCL extends Job implements IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
-
-		ResCL() {
-			super("Git resource decorator trigger");
-		}
-
-		GitResourceDecorator getActiveDecorator() {
-			IDecoratorManager decoratorManager = Activator.getDefault()
-					.getWorkbench().getDecoratorManager();
-			if (decoratorManager.getEnabled(decoratorId))
-				return (GitResourceDecorator) decoratorManager
-						.getLightweightLabelDecorator(decoratorId);
-			return null;
-		}
-
-		private Set<IResource> resources = new LinkedHashSet<IResource>();
-
-		public void refsChanged(RefsChangedEvent e) {
-			repositoryChanged(e);
-		}
-
-		public void indexChanged(IndexChangedEvent e) {
-			repositoryChanged(e);
-		}
-
-		private void repositoryChanged(RepositoryChangedEvent e) {
-			Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
-			for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
-				RepositoryMapping mapping = RepositoryMapping.getMapping(p);
-				if (mapping != null && mapping.getRepository() == e.getRepository())
-					ms.add(mapping);
-			}
-			for (RepositoryMapping m : ms) {
-				repositoryChanged(m);
-			}
-		}
-
-		public void repositoryChanged(final RepositoryMapping which) {
-			synchronized (resources) {
-				resources.add(which.getContainer());
-			}
-			schedule();
-		}
-
-		@Override
-		protected IStatus run(IProgressMonitor arg0) {
-			try {
-				if (resources.size() > 0) {
-					IResource m;
-					synchronized(resources) {
-						Iterator<IResource> i = resources.iterator();
-						m = i.next();
-						i.remove();
-
-						while (!m.isAccessible()) {
-							if (!i.hasNext())
-								return Status.OK_STATUS;
-							m = i.next();
-							i.remove();
-						}
-
-						if (resources.size() > 0)
-							schedule();
-					}
-					ISchedulingRule markerRule = m.getWorkspace().getRuleFactory().markerRule(m);
-					getJobManager().beginRule(markerRule, arg0);
-					try {
-						m.accept(new IResourceVisitor() {
-							public boolean visit(IResource resource) throws CoreException {
-								GitResourceDecorator decorator = getActiveDecorator();
-								if (decorator != null)
-									decorator.clearDecorationState(resource);
-								return true;
-							}
-						},
-						IResource.DEPTH_INFINITE,
-						true);
-					} finally {
-						getJobManager().endRule(markerRule);
-					}
-				}
-				return Status.OK_STATUS;
-			} catch (Exception e) {
-				// We must be silent here or the UI will panic with lots of error messages
-				Activator.logError("Failed to trigger resource re-decoration", e);
-				return Status.OK_STATUS;
-			}
-		}
-
-		public void resourceChanged(IResourceChangeEvent event) {
-			if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
-				return;
-			}
-			try {
-				event.getDelta().accept(new IResourceDeltaVisitor() {
-					public boolean visit(IResourceDelta delta)
-							throws CoreException {
-						for (IResource r = delta.getResource(); r.getType() != IResource.ROOT; r = r
-								.getParent()) {
-							synchronized (resources) {
-								resources.add(r);
-							}
-						}
-						return true;
-					}
-				},
-				true
-				);
-			} catch (Exception e) {
-				Activator.logError("Problem during decorations. Stopped", e);
-			}
-			schedule();
-		}
-
-		void force() {
-			for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
-				synchronized (resources) {
-					resources.add(p);
-				}
-			}
-			schedule();
-		}
-	} // End ResCL
-
-	void clearDecorationState(IResource r) throws CoreException {
-		if (r.isAccessible()) {
-			r.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, null);
-			fireLabelProviderChanged(new LabelProviderChangedEvent(this, r));
-		}
-	}
-
-	static ResCL myrescl = new ResCL();
-
-	static {
-		Repository.addAnyRepositoryChangedListener(myrescl);
-		GitProjectData.addRepositoryChangeListener(myrescl);
-		ResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl,
-				IResourceChangeEvent.POST_CHANGE);
-	}
-
-	/**
-	 * Request that the decorator be updated, to reflect any recent changes.
-	 * <p>
-	 * Can be invoked any any thread. If the current thread is not the UI
-	 * thread, an async update will be scheduled.
-	 * </p>
-	 */
-	public static void refresh() {
-		myrescl.force();
-	}
-
-	private static IResource toIResource(final Object e) {
-		if (e instanceof IResource)
-			return (IResource) e;
-		if (e instanceof IAdaptable) {
-			final Object c = ((IAdaptable) e).getAdapter(IResource.class);
-			if (c instanceof IResource)
-				return (IResource) c;
-		}
-		return null;
-	}
-
-	static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName(
-			"org.spearce.egit.ui.internal.decorators.GitResourceDecorator",
-			"dirty");
-
-	static final int UNCHANGED = 0;
-
-	static final int CHANGED = 1;
-
-	private Boolean isDirty(IResource rsrc) {
-		try {
-			if (rsrc.getType() == IResource.FILE && Team.isIgnored((IFile)rsrc))
-				return Boolean.FALSE;
-
-			RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
-			if (mapped != null) {
-				if (rsrc instanceof IContainer) {
-					for (IResource r : ((IContainer) rsrc)
-							.members(IContainer.EXCLUDE_DERIVED)) {
-						Boolean f = isDirty(r);
-						if (f == null || f.booleanValue())
-							return Boolean.TRUE;
-					}
-					return Boolean.FALSE;
-				}
-
-				return Boolean.valueOf(mapped.isResourceChanged(rsrc));
-			}
-			return null; // not mapped
-		} catch (CoreException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-		return null;
-	}
-
-	public void decorate(final Object element, final IDecoration decoration) {
-		final IResource rsrc = toIResource(element);
-		if (rsrc == null)
-			return;
-
-		// If the workspace has not been refreshed properly a resource might
-		// not actually exist, so we ignore these and do not decorate them
-		if (!rsrc.exists() && !rsrc.isPhantom()) {
-			Activator.trace("Tried to decorate non-existent resource "+rsrc);
-			return;
-		}
-
-		RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc);
-
-		// TODO: How do I see a renamed resource?
-		// TODO: Even trickier: when a path change from being blob to tree?
-		try {
-			if (mapped != null) {
-				Repository repository = mapped.getRepository();
-				GitIndex index = repository.getIndex();
-				String repoRelativePath = mapped.getRepoRelativePath(rsrc);
-
-				if (repoRelativePath == null) {
-					Activator.trace("Cannot decorate linked resource " + rsrc);
-					return;
-				}
-
-				Tree headTree = repository.mapTree(Constants.HEAD);
-				TreeEntry blob = headTree!=null ? headTree.findBlobMember(repoRelativePath) : null;
-				Entry entry = index.getEntry(repoRelativePath);
-				if (entry == null) {
-					if (blob == null) {
-						if (rsrc instanceof IContainer) {
-							Integer df = (Integer) rsrc
-									.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
-							Boolean f = df == null ? isDirty(rsrc)
-									: Boolean.valueOf(df.intValue() == CHANGED);
-							if (f != null) {
-								if (f.booleanValue()) {
-									decoration.addPrefix(">"); // Have not
-									// seen
-									orState(rsrc, CHANGED);
-								} else {
-									orState(rsrc, UNCHANGED);
-									// decoration.addSuffix("=?");
-								}
-							} else {
-								decoration.addSuffix(" ?* ");
-							}
-
-							if (rsrc instanceof IProject) {
-								Repository repo = mapped.getRepository();
-								try {
-									String branch = repo.getBranch();
-									RepositoryState repositoryState = repo.getRepositoryState();
-									String statename;
-									if (repositoryState.equals(RepositoryState.SAFE))
-										statename = "";
-									else
-										statename = repositoryState.getDescription() + " ";
-									decoration.addSuffix(" [Git " + statename + "@ " + branch + "]");
-								} catch (IOException e) {
-									e.printStackTrace();
-									decoration.addSuffix(" [Git ?]");
-								}
-								decoration.addOverlay(UIIcons.OVR_SHARED);
-							}
-
-						} else {
-							if (Team.isIgnoredHint(rsrc)) {
-								decoration.addSuffix("(ignored)");
-							} else {
-								decoration.addPrefix(">");
-								decoration.addSuffix("(untracked)");
-								orState(rsrc.getParent(), CHANGED);
-							}
-						}
-					} else {
-						if (!(rsrc instanceof IContainer)) {
-							decoration.addSuffix("(deprecated)"); // Will drop on
-							// commit
-							decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE);
-							orState(rsrc.getParent(), CHANGED);
-						}
-					}
-				} else {
-					if (entry.getStage() != GitIndex.STAGE_0) {
-						decoration.addSuffix("(conflict)");
-						decoration.addOverlay(UIIcons.OVR_CONFLICT);
-						orState(rsrc.getParent(), CHANGED);
-						return;
-					}
-
-					if (blob == null) {
-						decoration.addOverlay(UIIcons.OVR_PENDING_ADD);
-						orState(rsrc.getParent(), CHANGED);
-					} else {
-
-						if (entry.isAssumedValid()) {
-							decoration.addOverlay(UIIcons.OVR_ASSUMEVALID);
-							return;
-						}
-
-						decoration.addOverlay(UIIcons.OVR_SHARED);
-
-						if (entry.isModified(mapped.getWorkDir(), true)) {
-							decoration.addPrefix(">");
-							decoration.addSuffix("(not updated)");
-							orState(rsrc.getParent(), CHANGED);
-						} else {
-							if (!entry.getObjectId().equals(blob.getId()))
-								decoration.addPrefix(">");
-							else
-								decoration.addPrefix(""); // set it to avoid further calls
-						}
-					}
-				}
-			}
-		} catch (IOException e) {
-			decoration.addSuffix("?");
-			// If we throw an exception Eclipse will log the error and
-			// unregister us thereby preventing us from dragging down the
-			// entire workbench because we are crashing.
-			//
-			throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
-		} catch (CoreException e) {
-			throw new RuntimeException(UIText.Decorator_failedLazyLoading, e);
-		}
-	}
-
-	private void orState(final IResource rsrc, int flag) {
-		if (rsrc == null || rsrc.getType() == IResource.ROOT) {
-			return;
-		}
-
-		try {
-			Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY);
-			Runnable runnable = new Runnable() {
-				public void run() {
-					// Async could be called after a
-					// project is closed or a
-					// resource is deleted
-					if (!rsrc.isAccessible())
-						return;
-					fireLabelProviderChanged(new LabelProviderChangedEvent(
-							GitResourceDecorator.this, rsrc));
-				}
-			};
-			if (dirty == null) {
-				rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, new Integer(flag));
-				orState(rsrc.getParent(), flag);
-//				if (Thread.currentThread() == Display.getDefault().getThread())
-//					runnable.run();
-//				else
-					Display.getDefault().asyncExec(runnable);
-			} else {
-				if ((dirty.intValue() | flag) != dirty.intValue()) {
-					dirty = new Integer(dirty.intValue() | flag);
-					rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, dirty);
-					orState(rsrc.getParent(), dirty.intValue());
-//					if (Thread.currentThread() == Display.getDefault().getThread())
-//						runnable.run();
-//					else
-						Display.getDefault().asyncExec(runnable);
-				}
-			}
-		} catch (CoreException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
-	
-	@Override
-	public boolean isLabelProperty(Object element, String property) {
-		return super.isLabelProperty(element, property);
-	}
-}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
new file mode 100644
index 0000000..8d6c741
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.ui.internal.decorators;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * Represents the state of a resource that can be used as a basis for decoration
+ */
+public interface IDecoratableResource {
+
+	/**
+	 * Gets the type of the resource as defined by {@link IResource}
+	 * 
+	 * @return the type of the resource
+	 */
+	int getType();
+
+	/**
+	 * Gets the name of the resource
+	 * 
+	 * @return the name of the resource
+	 */
+	String getName();
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
new file mode 100644
index 0000000..84203b7
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -0,0 +1,702 @@
+/*******************************************************************************
+ * Copyright (C) 2003, 2006 Subclipse project and others.
+ * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.ui.internal.preferences;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.preference.PreferenceStore;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.DecorationContext;
+import org.eclipse.jface.viewers.DecorationOverlayIcon;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.IDecorationContext;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.TabFolder;
+import org.eclipse.swt.widgets.TabItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ListSelectionDialog;
+import org.eclipse.ui.ide.IDE.SharedImages;
+import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIPreferences;
+import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.SWTUtils;
+import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator.DecorationHelper;
+import org.spearce.egit.ui.internal.decorators.IDecoratableResource;
+
+/**
+ * Preference page for customizing Git label decorations
+ */
+public class GitDecoratorPreferencePage extends PreferencePage implements
+		IWorkbenchPreferencePage {
+
+	private Text fileTextFormat;
+
+	private Text folderTextFormat;
+
+	private Text projectTextFormat;
+
+	private Button showDirty;
+
+	private Preview preview;
+
+	private static final Collection PREVIEW_FILESYSTEM_ROOT;
+
+	private static IPropertyChangeListener themeListener;
+
+	static {
+		final PreviewResource project = new PreviewResource(
+				"Project", IResource.PROJECT); //$NON-NLS-1$1
+		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
+		children.add(new PreviewResource("folder", IResource.FOLDER)); //$NON-NLS-1$
+		children.add(new PreviewResource("file.txt", IResource.FILE)); //$NON-NLS-1$
+		project.children = children;
+		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
+	}
+
+	/**
+	 * Constructs a decorator preference page
+	 */
+	public GitDecoratorPreferencePage() {
+		setDescription(UIText.DecoratorPreferencesPage_description);
+	}
+
+	/**
+	 * @see PreferencePage#createContents(Composite)
+	 */
+	protected Control createContents(Composite parent) {
+
+		Composite composite = SWTUtils.createHVFillComposite(parent,
+				SWTUtils.MARGINS_NONE);
+
+		SWTUtils.createPreferenceLink(
+				(IWorkbenchPreferenceContainer) getContainer(), composite,
+				"org.eclipse.ui.preferencePages.Decorators",
+				UIText.DecoratorPreferencesPage_labelDecorationsLink); //$NON-NLS-1$
+
+		TabFolder tabFolder = new TabFolder(composite, SWT.NONE);
+		tabFolder.setLayoutData(SWTUtils.createHVFillGridData());
+
+		TabItem tabItem = new TabItem(tabFolder, SWT.NONE);
+		tabItem.setText(UIText.DecoratorPreferencesPage_generalTabFolder);
+		tabItem.setControl(createGeneralDecoratorPage(tabFolder));
+
+		tabItem = new TabItem(tabFolder, SWT.NONE);
+		tabItem.setText(UIText.DecoratorPreferencesPage_textLabel);
+		tabItem.setControl(createTextDecoratorPage(tabFolder));
+
+		tabItem = new TabItem(tabFolder, SWT.NONE);
+		tabItem.setText(UIText.DecoratorPreferencesPage_iconLabel);
+		tabItem.setControl(createIconDecoratorPage(tabFolder));
+
+		initializeValues();
+
+		preview = new Preview(composite);
+		preview.refresh();
+
+		// TODO: Add help text for this preference page
+
+		themeListener = new IPropertyChangeListener() {
+			public void propertyChange(PropertyChangeEvent event) {
+				preview.refresh();
+			}
+		};
+		PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(
+				themeListener);
+
+		Dialog.applyDialogFont(parent);
+
+		return composite;
+	}
+
+	private Control createGeneralDecoratorPage(Composite parent) {
+		Composite composite = SWTUtils.createHVFillComposite(parent,
+				SWTUtils.MARGINS_DEFAULT);
+
+		showDirty = SWTUtils.createCheckBox(composite,
+				UIText.DecoratorPreferencesPage_computeDeep);
+
+		return composite;
+	}
+
+	/**
+	 * Creates the controls for the first tab folder
+	 * 
+	 * @param parent
+	 * 
+	 * @return the control
+	 */
+	private Control createTextDecoratorPage(Composite parent) {
+		Composite fileTextGroup = SWTUtils.createHVFillComposite(parent,
+				SWTUtils.MARGINS_DEFAULT, 3);
+
+		TextPair format = createFormatEditorControl(fileTextGroup,
+				UIText.DecoratorPreferencesPage_fileFormatLabel,
+				UIText.DecoratorPreferencesPage_addVariablesAction,
+				getFileBindingDescriptions());
+		fileTextFormat = format.t1;
+
+		format = createFormatEditorControl(fileTextGroup,
+				UIText.DecoratorPreferencesPage_folderFormatLabel,
+				UIText.DecoratorPreferencesPage_addVariablesAction,
+				getFolderBindingDescriptions());
+		folderTextFormat = format.t1;
+
+		format = createFormatEditorControl(fileTextGroup,
+				UIText.DecoratorPreferencesPage_projectFormatLabel,
+				UIText.DecoratorPreferencesPage_addVariablesAction,
+				getProjectBindingDescriptions());
+		projectTextFormat = format.t1;
+
+		return fileTextGroup;
+	}
+
+	private Control createIconDecoratorPage(Composite parent) {
+		Composite imageGroup = SWTUtils.createHVFillComposite(parent,
+				SWTUtils.MARGINS_DEFAULT, 2);
+
+		return imageGroup;
+	}
+
+	private TextPair createFormatEditorControl(Composite composite,
+			String title, String buttonText, final Map supportedBindings) {
+
+		SWTUtils.createLabel(composite, title);
+
+		Text format = new Text(composite, SWT.BORDER);
+		format.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+		format.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				updatePreview();
+			}
+		});
+		Button b = new Button(composite, SWT.NONE);
+		b.setText(buttonText);
+		GridData data = new GridData();
+		data.horizontalAlignment = GridData.FILL;
+		int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
+		data.widthHint = Math.max(widthHint, b.computeSize(SWT.DEFAULT,
+				SWT.DEFAULT, true).x);
+		b.setLayoutData(data);
+		final Text formatToInsert = format;
+		b.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				addVariables(formatToInsert, supportedBindings);
+			}
+		});
+
+		return new TextPair(format, null);
+	}
+
+	/**
+	 * Initializes states of the controls from the preference store.
+	 */
+	private void initializeValues() {
+		final IPreferenceStore store = getPreferenceStore();
+
+		fileTextFormat.setText(store
+				.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
+		folderTextFormat.setText(store
+				.getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION));
+		projectTextFormat.setText(store
+				.getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
+
+		showDirty.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+
+		setValid(true);
+	}
+
+	/**
+	 * @see IWorkbenchPreferencePage#init(IWorkbench)
+	 */
+	public void init(IWorkbench workbench) {
+		// No-op
+	}
+
+	/**
+	 * OK was clicked. Store the preferences to the plugin store
+	 * 
+	 * @return whether it is okay to close the preference page
+	 */
+	public boolean performOk() {
+		IPreferenceStore store = getPreferenceStore();
+		final boolean okToClose = performOk(store);
+		if (store.needsSaving()) {
+			Activator.getDefault().savePluginPreferences();
+			Activator.broadcastPropertyChange(new PropertyChangeEvent(this,
+					Activator.DECORATORS_CHANGED, null, null));
+		}
+		return okToClose;
+	}
+
+	/**
+	 * Store the preferences to the given preference store
+	 * 
+	 * @param store
+	 *            the preference store to store the preferences to
+	 * 
+	 * @return whether it operation succeeded
+	 */
+	private boolean performOk(IPreferenceStore store) {
+
+		store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION,
+				fileTextFormat.getText());
+		store.setValue(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
+				folderTextFormat.getText());
+		store.setValue(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
+				projectTextFormat.getText());
+
+		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY, showDirty
+				.getSelection());
+
+		return true;
+	}
+
+	/**
+	 * Defaults was clicked. Restore the Git decoration preferences to their
+	 * default values
+	 */
+	protected void performDefaults() {
+		super.performDefaults();
+		IPreferenceStore store = getPreferenceStore();
+
+		fileTextFormat.setText(store
+				.getDefaultString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
+		folderTextFormat
+				.setText(store
+						.getDefaultString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION));
+		projectTextFormat
+				.setText(store
+						.getDefaultString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
+
+		showDirty.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+	}
+
+	/**
+	 * Returns the preference store that belongs to the our plugin.
+	 * 
+	 * This is important because we want to store our preferences separately
+	 * from the desktop.
+	 * 
+	 * @return the preference store for this plugin
+	 */
+	protected IPreferenceStore doGetPreferenceStore() {
+		return Activator.getDefault().getPreferenceStore();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.jface.dialogs.DialogPage#dispose()
+	 */
+	public void dispose() {
+		PlatformUI.getWorkbench().getThemeManager()
+				.removePropertyChangeListener(themeListener);
+		super.dispose();
+	}
+
+	/**
+	 * Adds another variable to the given target text
+	 * 
+	 * A ListSelectionDialog pops up and allow the user to choose the variable,
+	 * which is then inserted at current position in <code>text</code>
+	 * 
+	 * @param target
+	 *            the target to add the variable to
+	 * @param bindings
+	 *            the map of bindings
+	 */
+	private void addVariables(Text target, Map bindings) {
+
+		final List<StringPair> variables = new ArrayList<StringPair>(bindings
+				.size());
+
+		ILabelProvider labelProvider = new LabelProvider() {
+			public String getText(Object element) {
+				return ((StringPair) element).s1
+						+ " - " + ((StringPair) element).s2; //$NON-NLS-1$
+			}
+		};
+
+		IStructuredContentProvider contentsProvider = new IStructuredContentProvider() {
+			public Object[] getElements(Object inputElement) {
+				return variables.toArray(new StringPair[variables.size()]);
+			}
+
+			public void dispose() {
+				// No-op
+			}
+
+			public void inputChanged(Viewer viewer, Object oldInput,
+					Object newInput) {
+				// No-op
+			}
+		};
+
+		for (Iterator it = bindings.keySet().iterator(); it.hasNext();) {
+			StringPair variable = new StringPair();
+			variable.s1 = (String) it.next(); // variable
+			variable.s2 = (String) bindings.get(variable.s1); // description
+			variables.add(variable);
+		}
+
+		ListSelectionDialog dialog = new ListSelectionDialog(this.getShell(),
+				this, contentsProvider, labelProvider,
+				UIText.DecoratorPreferencesPage_selectVariablesToAdd);
+		dialog.setTitle(UIText.DecoratorPreferencesPage_addVariablesTitle);
+		if (dialog.open() != Window.OK)
+			return;
+
+		Object[] result = dialog.getResult();
+
+		for (int i = 0; i < result.length; i++) {
+			target.insert("{" + ((StringPair) result[i]).s1 + "}"); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+	}
+
+	class StringPair {
+		String s1;
+
+		String s2;
+	}
+
+	class TextPair {
+		TextPair(Text t1, Text t2) {
+			this.t1 = t1;
+			this.t2 = t2;
+		}
+
+		Text t1;
+
+		Text t2;
+	}
+
+	/**
+	 * Gets the map of bindings between variables and description, to use for
+	 * the format editors for files
+	 * 
+	 * @return the bindings
+	 */
+	private Map getFileBindingDescriptions() {
+		Map<String, String> bindings = new HashMap<String, String>();
+		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
+				UIText.DecoratorPreferencesPage_nameResourceVariable);
+		return bindings;
+	}
+
+	/**
+	 * Gets the map of bindings between variables and description, to use for
+	 * the format editors for folders
+	 * 
+	 * @return the bindings
+	 */
+	private Map getFolderBindingDescriptions() {
+		Map<String, String> bindings = new HashMap<String, String>();
+		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
+				UIText.DecoratorPreferencesPage_nameResourceVariable);
+		return bindings;
+	}
+
+	/**
+	 * Gets the map of bindings between variables and description, to use for
+	 * the format editors for projects
+	 * 
+	 * @return the bindings
+	 */
+	private Map getProjectBindingDescriptions() {
+		Map<String, String> bindings = new HashMap<String, String>();
+		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
+				UIText.DecoratorPreferencesPage_nameResourceVariable);
+		return bindings;
+	}
+
+	private void updatePreview() {
+		if (preview != null)
+			preview.refresh();
+	}
+
+	/**
+	 * Preview control for showing how changes in the dialog will affect
+	 * decoration
+	 */
+	private class Preview extends LabelProvider implements Observer,
+			ITreeContentProvider {
+
+		private final ResourceManager fImageCache;
+
+		private final TreeViewer fViewer;
+
+		private DecorationHelper fHelper;
+
+		public Preview(Composite composite) {
+			reloadDecorationHelper(); // Has to happen before the tree control
+										// is constructed
+			SWTUtils.createLabel(composite,
+					UIText.DecoratorPreferencesPage_preview);
+			fImageCache = new LocalResourceManager(JFaceResources
+					.getResources());
+
+			fViewer = new TreeViewer(composite);
+			fViewer.getControl().setLayoutData(SWTUtils.createHVFillGridData());
+			fViewer.setContentProvider(this);
+			fViewer.setLabelProvider(this);
+			fViewer.setInput(PREVIEW_FILESYSTEM_ROOT);
+			fViewer.expandAll();
+			fHelper = new DecorationHelper(new PreferenceStore());
+		}
+
+		private void reloadDecorationHelper() {
+			PreferenceStore store = new PreferenceStore();
+			performOk(store);
+			fHelper = new DecorationHelper(store);
+		}
+
+		public void refresh() {
+			reloadDecorationHelper();
+			fViewer.refresh(true);
+			setColorsAndFonts(fViewer.getTree().getItems());
+		}
+
+		@SuppressWarnings("unused")
+		private void setColorsAndFonts(TreeItem[] items) {
+			// TODO: Implement colors and fonts
+		}
+
+		public void update(Observable o, Object arg) {
+			refresh();
+		}
+
+		public Object[] getChildren(Object parentElement) {
+			return ((PreviewResource) parentElement).children.toArray();
+		}
+
+		public Object getParent(Object element) {
+			return null;
+		}
+
+		public boolean hasChildren(Object element) {
+			return !((PreviewResource) element).children.isEmpty();
+		}
+
+		public Object[] getElements(Object inputElement) {
+			return ((Collection) inputElement).toArray();
+		}
+
+		public void dispose() {
+			fImageCache.dispose();
+		}
+
+		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+			// No-op
+		}
+
+		public Color getBackground(Object element) {
+			return getDecoration(element).getBackgroundColor();
+		}
+
+		public Color getForeground(Object element) {
+			return getDecoration(element).getForegroundColor();
+		}
+
+		public Font getFont(Object element) {
+			return getDecoration(element).getFont();
+		}
+
+		public String getText(Object element) {
+			final PreviewDecoration decoration = getDecoration(element);
+			final StringBuffer buffer = new StringBuffer();
+			final String prefix = decoration.getPrefix();
+			if (prefix != null)
+				buffer.append(prefix);
+			buffer.append(((PreviewResource) element).getName());
+			final String suffix = decoration.getSuffix();
+			if (suffix != null)
+				buffer.append(suffix);
+			return buffer.toString();
+		}
+
+		public Image getImage(Object element) {
+			final String s;
+			switch (((PreviewResource) element).type) {
+			case IResource.PROJECT:
+				s = SharedImages.IMG_OBJ_PROJECT;
+				break;
+			case IResource.FOLDER:
+				s = ISharedImages.IMG_OBJ_FOLDER;
+				break;
+			default:
+				s = ISharedImages.IMG_OBJ_FILE;
+				break;
+			}
+			final Image baseImage = PlatformUI.getWorkbench().getSharedImages()
+					.getImage(s);
+			final ImageDescriptor overlay = getDecoration(element).getOverlay();
+			if (overlay == null)
+				return baseImage;
+			try {
+				return fImageCache.createImage(new DecorationOverlayIcon(
+						baseImage, overlay, IDecoration.BOTTOM_RIGHT));
+			} catch (Exception e) {
+				Activator.logError(e.getMessage(), e);
+			}
+
+			return null;
+		}
+
+		private PreviewDecoration getDecoration(Object element) {
+			PreviewDecoration decoration = new PreviewDecoration();
+			fHelper.decorate(decoration, (PreviewResource) element);
+			return decoration;
+		}
+	}
+
+	private static class PreviewResource implements IDecoratableResource {
+		public final String name;
+
+		public final int type;
+
+		public Collection children;
+
+		public PreviewResource(String name, int type) {
+			this.name = name;
+			this.type = type;
+			this.children = Collections.EMPTY_LIST;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public int getType() {
+			return type;
+		}
+	}
+
+	private class PreviewDecoration implements IDecoration {
+
+		private List<String> prefixes = new ArrayList<String>();
+
+		private List<String> suffixes = new ArrayList<String>();
+
+		private ImageDescriptor overlay = null;
+
+		private Font font;
+
+		private Color backgroundColor;
+
+		private Color foregroundColor;
+
+		public void addOverlay(ImageDescriptor overlayImage) {
+			overlay = overlayImage;
+		}
+
+		public void addOverlay(ImageDescriptor overlayImage, int quadrant) {
+			overlay = overlayImage;
+		}
+
+		public void addPrefix(String prefix) {
+			prefixes.add(prefix);
+		}
+
+		public void addSuffix(String suffix) {
+			suffixes.add(suffix);
+		}
+
+		public IDecorationContext getDecorationContext() {
+			return new DecorationContext();
+		}
+
+		public void setBackgroundColor(Color color) {
+			backgroundColor = color;
+		}
+
+		public void setForegroundColor(Color color) {
+			foregroundColor = color;
+		}
+
+		public void setFont(Font font) {
+			this.font = font;
+		}
+
+		public ImageDescriptor getOverlay() {
+			return overlay;
+		}
+
+		public String getPrefix() {
+			StringBuffer sb = new StringBuffer();
+			for (Iterator<String> iter = prefixes.iterator(); iter.hasNext();) {
+				sb.append(iter.next());
+			}
+			return sb.toString();
+		}
+
+		public String getSuffix() {
+			StringBuffer sb = new StringBuffer();
+			for (Iterator<String> iter = suffixes.iterator(); iter.hasNext();) {
+				sb.append(iter.next());
+			}
+			return sb.toString();
+		}
+
+		public Font getFont() {
+			return font;
+		}
+
+		public Color getBackgroundColor() {
+			return backgroundColor;
+		}
+
+		public Color getForegroundColor() {
+			return foregroundColor;
+		}
+	}
+}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index c1d9db6..4660983 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -169,7 +169,6 @@ RefSpecPage_annotatedTagsAutoFollow=Automatically follow tags if we fetch the th
 RefSpecPage_annotatedTagsFetchTags=Always fetch tags, even if we do not have the thing it points at
 RefSpecPage_annotatedTagsNoTags=Never fetch tags, even if we have the thing it points at
 
-Decorator_failedLazyLoading=Resource decorator failed to load tree contents on demand.
 QuickDiff_failedLoading=Quick diff failed to obtain file data.
 
 ResourceHistory_toggleCommentWrap=Wrap Comments
@@ -346,3 +345,27 @@ BranchSelectionDialog_ResetTypeMixed=&Mixed (working directory unmodified)
 BranchSelectionDialog_ResetTypeSoft=&Soft (Index and working directory unmodified)
 BranchSelectionDialog_Tags=Tags
 BranchSelectionDialog_Refs=&Refs
+
+Decorator_exceptionMessage=Errors occurred while applying Git decorations to resources.
+
+DecoratorPreferencesPage_addVariablesTitle=Add Variables
+DecoratorPreferencesPage_addVariablesAction=Add &Variables...
+DecoratorPreferencesPage_computeDeep=Include &ancestors when re-decorating changed resources
+DecoratorPreferencesPage_description=Shows Git specific information on resources in projects under version control.
+
+DecoratorPreferencesPage_decorationSettings=Decoration &settings:
+DecoratorPreferencesPage_preview=Preview:
+DecoratorPreferencesPage_fileFormatLabel=&Files:
+DecoratorPreferencesPage_folderFormatLabel=F&olders:
+DecoratorPreferencesPage_projectFormatLabel=&Projects:
+DecoratorPreferencesPage_fileFormatDefault={name}
+DecoratorPreferencesPage_folderFormatDefault={name}
+DecoratorPreferencesPage_projectFormatDefault={name}
+DecoratorPreferencesPage_labelDecorationsLink=See <a>''{0}''</a> to enable or disable Git decorations.
+DecoratorPreferencesPage_generalTabFolder=&General
+DecoratorPreferencesPage_nameResourceVariable=name of the resource being decorated
+DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and project text labels:
+DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format:
+DecoratorPreferencesPage_textLabel=T&ext Decorations
+DecoratorPreferencesPage_iconLabel=&Icon Decorations
+
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 07/12] Add binding for name of the current branch
  2009-02-11 18:40           ` [EGIT PATCH v2 06/12] Implement basic customizable label decorations with preferences Tor Arne Vestbø
@ 2009-02-11 18:40             ` Tor Arne Vestbø
  2009-02-11 18:40               ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

This is an example of how to add more bindings to the
decoration preferences, and how they are implemented in
the decorator.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../src/org/spearce/egit/ui/UIText.java            |    3 ++
 .../decorators/GitLightweightDecorator.java        |   26 ++++++++++++++++---
 .../internal/decorators/IDecoratableResource.java  |    8 ++++++
 .../preferences/GitDecoratorPreferencePage.java    |   21 +++++++++++----
 .../src/org/spearce/egit/ui/uitext.properties      |    3 +-
 5 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index a7ef408..345c66b 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -971,6 +971,9 @@
 	public static String DecoratorPreferencesPage_nameResourceVariable;
 
 	/** */
+	public static String DecoratorPreferencesPage_bindingBranchName;
+
+	/** */
 	public static String DecoratorPreferencesPage_selectFormats;
 
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index 85b9173..265d5a3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -13,6 +13,7 @@
 
 package org.spearce.egit.ui.internal.decorators;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -44,6 +45,7 @@
 import org.eclipse.team.ui.TeamUI;
 import org.eclipse.ui.IContributorResourceAdapter;
 import org.eclipse.ui.PlatformUI;
+import org.spearce.egit.core.GitException;
 import org.spearce.egit.core.internal.util.ExceptionCollector;
 import org.spearce.egit.core.project.GitProjectData;
 import org.spearce.egit.core.project.RepositoryChangeListener;
@@ -157,17 +159,26 @@ public void decorate(Object element, IDecoration decoration) {
 		if (activator == null)
 			return;
 
-		DecorationHelper helper = new DecorationHelper(activator
-				.getPreferenceStore());
-		helper.decorate(decoration, new DecoratableResourceAdapter(resource));
+		try {
+			DecorationHelper helper = new DecorationHelper(activator
+					.getPreferenceStore());
+			helper.decorate(decoration,
+					new DecoratableResourceAdapter(resource));
+		} catch (IOException e) {
+			handleException(resource, GitException.wrapException(e));
+		}
 	}
 
 	private class DecoratableResourceAdapter implements IDecoratableResource {
 
 		private IResource resource;
+		private String branch;
 
-		public DecoratableResourceAdapter(IResource resourceToWrap) {
+		public DecoratableResourceAdapter(IResource resourceToWrap) throws IOException {
 			resource = resourceToWrap;
+			RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
+			Repository repository = mapping.getRepository();
+			branch = repository.getBranch();
 		}
 
 		public String getName() {
@@ -177,6 +188,10 @@ public String getName() {
 		public int getType() {
 			return resource.getType();
 		}
+
+		public String getBranch() {
+			return branch;
+		}
 	}
 
 	/**
@@ -192,6 +207,8 @@ public int getType() {
 
 		/** */
 		public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
+		/** */
+		public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
 
 		/**
 		 * Constructs a decorator using the rules from the given
@@ -234,6 +251,7 @@ public void decorate(IDecoration decoration,
 
 			Map<String, String> bindings = new HashMap<String, String>();
 			bindings.put(BINDING_RESOURCE_NAME, resource.getName());
+			bindings.put(BINDING_BRANCH_NAME, resource.getBranch());
 
 			decorate(decoration, format, bindings);
 		}
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
index 8d6c741..6b36e0e 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
@@ -28,4 +28,12 @@
 	 * @return the name of the resource
 	 */
 	String getName();
+
+	/**
+	 * Gets the current branch of the resource if applicable
+	 * 
+	 * @return the name of the current branch, or <code>null</code> if not
+	 *         applicable
+	 */
+	String getBranch();
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
index 84203b7..b7d737c 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -93,10 +93,10 @@
 
 	static {
 		final PreviewResource project = new PreviewResource(
-				"Project", IResource.PROJECT); //$NON-NLS-1$1
+				"Project", IResource.PROJECT, "master"); //$NON-NLS-1$1
 		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
-		children.add(new PreviewResource("folder", IResource.FOLDER)); //$NON-NLS-1$
-		children.add(new PreviewResource("file.txt", IResource.FILE)); //$NON-NLS-1$
+		children.add(new PreviewResource("folder", IResource.FOLDER, null)); //$NON-NLS-1$
+		children.add(new PreviewResource("file.txt", IResource.FILE, null)); //$NON-NLS-1$
 		project.children = children;
 		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
 	}
@@ -456,6 +456,8 @@ private Map getProjectBindingDescriptions() {
 		Map<String, String> bindings = new HashMap<String, String>();
 		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
 				UIText.DecoratorPreferencesPage_nameResourceVariable);
+		bindings.put(DecorationHelper.BINDING_BRANCH_NAME,
+				UIText.DecoratorPreferencesPage_bindingBranchName);
 		return bindings;
 	}
 
@@ -478,8 +480,8 @@ private void updatePreview() {
 		private DecorationHelper fHelper;
 
 		public Preview(Composite composite) {
-			reloadDecorationHelper(); // Has to happen before the tree control
-										// is constructed
+			// Has to happen before the tree control is constructed
+			reloadDecorationHelper();
 			SWTUtils.createLabel(composite,
 					UIText.DecoratorPreferencesPage_preview);
 			fImageCache = new LocalResourceManager(JFaceResources
@@ -602,12 +604,15 @@ private PreviewDecoration getDecoration(Object element) {
 	private static class PreviewResource implements IDecoratableResource {
 		public final String name;
 
+		public final String branch;
+
 		public final int type;
 
 		public Collection children;
 
-		public PreviewResource(String name, int type) {
+		public PreviewResource(String name, int type, String branch) {
 			this.name = name;
+			this.branch = branch;
 			this.type = type;
 			this.children = Collections.EMPTY_LIST;
 		}
@@ -619,6 +624,10 @@ public String getName() {
 		public int getType() {
 			return type;
 		}
+
+		public String getBranch() {
+			return branch;
+		}
 	}
 
 	private class PreviewDecoration implements IDecoration {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 4660983..d050be9 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -360,10 +360,11 @@ DecoratorPreferencesPage_folderFormatLabel=F&olders:
 DecoratorPreferencesPage_projectFormatLabel=&Projects:
 DecoratorPreferencesPage_fileFormatDefault={name}
 DecoratorPreferencesPage_folderFormatDefault={name}
-DecoratorPreferencesPage_projectFormatDefault={name}
+DecoratorPreferencesPage_projectFormatDefault={name} [{branch}]
 DecoratorPreferencesPage_labelDecorationsLink=See <a>''{0}''</a> to enable or disable Git decorations.
 DecoratorPreferencesPage_generalTabFolder=&General
 DecoratorPreferencesPage_nameResourceVariable=name of the resource being decorated
+DecoratorPreferencesPage_bindingBranchName=current branch of the project
 DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and project text labels:
 DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format:
 DecoratorPreferencesPage_textLabel=T&ext Decorations
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources
  2009-02-11 18:40             ` [EGIT PATCH v2 07/12] Add binding for name of the current branch Tor Arne Vestbø
@ 2009-02-11 18:40               ` Tor Arne Vestbø
  2009-02-11 18:40                 ` [EGIT PATCH v2 09/12] Implement icon and text decorations of various resource states Tor Arne Vestbø
  2009-02-11 22:16                 ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Robin Rosenberg
  0 siblings, 2 replies; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Can be enabled/disabled in the preferences

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.ui/icons/ovr/shared.gif           |  Bin 106 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/untracked.gif        |  Bin 0 -> 79 bytes
 .../egit/ui/PluginPreferenceInitializer.java       |    4 +-
 .../src/org/spearce/egit/ui/UIIcons.java           |    6 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    4 +
 .../src/org/spearce/egit/ui/UIText.java            |    6 +
 .../decorators/GitLightweightDecorator.java        |  162 ++++++++++++++++++--
 .../internal/decorators/IDecoratableResource.java  |   15 ++
 .../preferences/GitDecoratorPreferencePage.java    |   89 +++++++++--
 .../src/org/spearce/egit/ui/uitext.properties      |    2 +
 10 files changed, 259 insertions(+), 29 deletions(-)
 delete mode 100644 org.spearce.egit.ui/icons/ovr/shared.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/untracked.gif

diff --git a/org.spearce.egit.ui/icons/ovr/shared.gif b/org.spearce.egit.ui/icons/ovr/shared.gif
deleted file mode 100644
index eb71a3c742e133c2ed61c958e237c71e0f7cb6aa..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?wbhEHbWM|-D*v!E2|KHF5k5c|0Q~7_){{ITmE8U{M-dx?8EBgQce+Fzo@h1x-
p15m3DND^cQ1B<x8$xa82iYrMhmeJo^<v0pdEtw8K%o1d<1^~aoAP)cl

diff --git a/org.spearce.egit.ui/icons/ovr/untracked.gif b/org.spearce.egit.ui/icons/ovr/untracked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..45ca32060700d71abcb88bbcdb1000d503fe11cf
GIT binary patch
literal 79
zcmZ?wbhEHbWM|-DSj51<z}U+mGMhnsAy57};pVIV!2l?%_>+Z^fq{)d2gqgssbydm
biP#m!(lAAgdxkboj%vJ&f(EaJDuXouU6m8v

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
index 79c2665..7465444 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
@@ -35,13 +35,15 @@ public void initializeDefaultPreferences() {
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, true);
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_TOOLTIPS, false);
 
+		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
 		prefs.setDefault(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_fileFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_folderFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_projectFormatDefault);
-		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_TRACKED_ICON, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON, true);
 
 		w = new int[] { 500, 500 };
 		UIPreferences.setDefault(prefs,
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
index ced186e..4c0d189 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
@@ -24,8 +24,8 @@
 	/** Decoration for resource removed from the index but not commit. */
 	public static final ImageDescriptor OVR_PENDING_REMOVE;
 
-	/** Decoration for resource tracked and committed in git. */
-	public static final ImageDescriptor OVR_SHARED;
+	/** Decoration for resource not being tracked by Git */
+	public static final ImageDescriptor OVR_UNTRACKED;
 
 	/** Decoration for tracked resource with a merge conflict.  */
 	public static final ImageDescriptor OVR_CONFLICT;
@@ -86,7 +86,7 @@
 		base = init();
 		OVR_PENDING_ADD = map("ovr/pending_add.gif");
 		OVR_PENDING_REMOVE = map("ovr/pending_remove.gif");
-		OVR_SHARED = map("ovr/shared.gif");
+		OVR_UNTRACKED = map("ovr/untracked.gif");
 		OVR_CONFLICT = map("ovr/conflict.gif");
 		OVR_ASSUMEVALID = map("ovr/assumevalid.gif");
 		ELCL16_FIND = map("elcl16/find.gif");
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
index a6168a0..7916cea 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
@@ -60,6 +60,10 @@
 	public final static String DECORATOR_FOLDERTEXT_DECORATION = "decorator_foldertext_decoration";
 	/** */
 	public final static String DECORATOR_PROJECTTEXT_DECORATION = "decorator_projecttext_decoration";
+	/** */
+	public final static String DECORATOR_SHOW_TRACKED_ICON = "decorator_show_tracked_icon";
+	/** */
+	public final static String DECORATOR_SHOW_UNTRACKED_ICON = "decorator_show_untracked_icon";
 
 	/**
 	 * Get the preference values associated with a fixed integer array.
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 345c66b..60e4eaa 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -988,6 +988,12 @@
 	/** */
 	public static String DecoratorPreferencesPage_labelDecorationsLink;
 
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowTracked;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowUntracked;
+
 	static {
 		initializeMessages(UIText.class.getPackage().getName() + ".uitext",
 				UIText.class);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index 265d5a3..b20070a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -15,6 +15,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -34,6 +35,7 @@
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.IDecoration;
@@ -41,7 +43,11 @@
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
 import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.team.core.Team;
+import org.eclipse.team.ui.ISharedImages;
+import org.eclipse.team.ui.TeamImages;
 import org.eclipse.team.ui.TeamUI;
 import org.eclipse.ui.IContributorResourceAdapter;
 import org.eclipse.ui.PlatformUI;
@@ -51,13 +57,22 @@
 import org.spearce.egit.core.project.RepositoryChangeListener;
 import org.spearce.egit.core.project.RepositoryMapping;
 import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheIterator;
+import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.ObjectId;
 import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.lib.RepositoryChangedEvent;
 import org.spearce.jgit.lib.RepositoryListener;
+import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.treewalk.EmptyTreeIterator;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 
 /**
  * Supplies annotations for displayed resources
@@ -144,7 +159,7 @@ public void decorate(Object element, IDecoration decoration) {
 		if (!resource.exists() && !resource.isPhantom())
 			return;
 
-		// Make sure we're dealing with a Git project
+		// Make sure we're dealing with a project under Git revision control
 		final RepositoryMapping mapping = RepositoryMapping
 				.getMapping(resource);
 		if (mapping == null)
@@ -171,14 +186,80 @@ public void decorate(Object element, IDecoration decoration) {
 
 	private class DecoratableResourceAdapter implements IDecoratableResource {
 
-		private IResource resource;
-		private String branch;
+		private final IResource resource;
 
-		public DecoratableResourceAdapter(IResource resourceToWrap) throws IOException {
+		private final RepositoryMapping mapping;
+
+		private final Repository repository;
+
+		private final ObjectId headId;
+
+		private String branch = "";
+
+		private boolean tracked = false;
+
+		private boolean ignored = false;
+
+		public DecoratableResourceAdapter(IResource resourceToWrap)
+				throws IOException {
 			resource = resourceToWrap;
-			RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
-			Repository repository = mapping.getRepository();
+			mapping = RepositoryMapping.getMapping(resource);
+			repository = mapping.getRepository();
+			headId = repository.resolve(Constants.HEAD);
+
+			initializeValues();
+		}
+
+		/**
+		 * Initialize the various values that are used for making decoration
+		 * decisions later on.
+		 * 
+		 * We might as well pre-load these now, instead of using lazy
+		 * initialization, because they are all read by the decorator when
+		 * building variable bindings and computing the preferred overlay.
+		 * 
+		 * @throws IOException
+		 */
+		private void initializeValues() throws IOException {
+
+			// Resolve current branch
 			branch = repository.getBranch();
+
+			// Resolve tracked state
+			if (getType() == IResource.PROJECT) {
+				tracked = true;
+			} else {
+				final TreeWalk treeWalk = new TreeWalk(repository);
+
+				Set<String> repositoryPaths = Collections.singleton(mapping
+						.getRepoRelativePath(resource));
+				if (!(repositoryPaths.isEmpty() || repositoryPaths.contains(""))) {
+					treeWalk.setFilter(PathFilterGroup
+							.createFromStrings(repositoryPaths));
+					treeWalk.setRecursive(treeWalk.getFilter()
+							.shouldBeRecursive());
+					treeWalk.reset();
+
+					if (headId != null)
+						treeWalk.addTree(new RevWalk(repository)
+								.parseTree(headId));
+					else
+						treeWalk.addTree(new EmptyTreeIterator());
+
+					treeWalk.addTree(new DirCacheIterator(DirCache
+							.read(repository)));
+					if (treeWalk.next()) {
+						tracked = true;
+					}
+				}
+			}
+
+			// Resolve ignored state (currently only reads the global Eclipse
+			// ignores)
+			// TODO: Also read ignores from .git/info/excludes et al.
+			if (Team.isIgnoredHint(resource)) {
+				ignored = true;
+			}
 		}
 
 		public String getName() {
@@ -192,6 +273,14 @@ public int getType() {
 		public String getBranch() {
 			return branch;
 		}
+
+		public boolean isTracked() {
+			return tracked;
+		}
+
+		public boolean isIgnored() {
+			return ignored;
+		}
 	}
 
 	/**
@@ -203,13 +292,45 @@ public String getBranch() {
 	 */
 	public static class DecorationHelper {
 
-		private IPreferenceStore store;
-
 		/** */
 		public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
+
 		/** */
 		public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
 
+		private IPreferenceStore store;
+
+		/**
+		 * Define a cached image descriptor which only creates the image data
+		 * once
+		 */
+		private static class CachedImageDescriptor extends ImageDescriptor {
+			ImageDescriptor descriptor;
+
+			ImageData data;
+
+			public CachedImageDescriptor(ImageDescriptor descriptor) {
+				this.descriptor = descriptor;
+			}
+
+			public ImageData getImageData() {
+				if (data == null) {
+					data = descriptor.getImageData();
+				}
+				return data;
+			}
+		}
+
+		private static ImageDescriptor trackedImage;
+
+		private static ImageDescriptor untrackedImage;
+
+		static {
+			trackedImage = new CachedImageDescriptor(TeamImages
+					.getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR));
+			untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED);
+		}
+
 		/**
 		 * Constructs a decorator using the rules from the given
 		 * <code>preferencesStore</code>
@@ -233,6 +354,12 @@ public DecorationHelper(IPreferenceStore preferencesStore) {
 		 */
 		public void decorate(IDecoration decoration,
 				IDecoratableResource resource) {
+			decorateText(decoration, resource);
+			decorateIcons(decoration, resource);
+		}
+
+		private void decorateText(IDecoration decoration,
+				IDecoratableResource resource) {
 			String format = "";
 			switch (resource.getType()) {
 			case IResource.FILE:
@@ -256,9 +383,24 @@ public void decorate(IDecoration decoration,
 			decorate(decoration, format, bindings);
 		}
 
+		private void decorateIcons(IDecoration decoration,
+				IDecoratableResource resource) {
+			if (resource.isIgnored())
+				return;
+
+			if (resource.isTracked()) {
+				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON))
+					decoration.addOverlay(trackedImage);
+			} else if (store
+					.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
+				decoration.addOverlay(untrackedImage);
+			}
+		}
+
 		/**
-		 * Decorates the given <code>decoration</code>, using the given
-		 * <code>format</code>, and mapped using <code>bindings</code>
+		 * Decorates the given <code>decoration</code>, using the specified text
+		 * <code>format</code>, and mapped using the variable bindings from
+		 * <code>bindings</code>
 		 * 
 		 * @param decoration
 		 *            the decoration to decorate
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
index 6b36e0e..f144214 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
@@ -36,4 +36,19 @@
 	 *         applicable
 	 */
 	String getBranch();
+
+	/**
+	 * Returns whether or not the resource is tracked by Git
+	 * 
+	 * @return whether or not the resource is tracked by Git
+	 */
+	boolean isTracked();
+
+	/**
+	 * Returns whether or not the resource is ignored, either by a global team
+	 * ignore in Eclipse, or by .git/info/exclude et al.
+	 * 
+	 * @return whether or not the resource is ignored
+	 */
+	boolean isIgnored();
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
index b7d737c..7b637e3 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -44,6 +44,9 @@
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
@@ -83,7 +86,11 @@
 
 	private Text projectTextFormat;
 
-	private Button showDirty;
+	private Button computeDeepDirtyState;
+
+	private Button showTracked;
+
+	private Button showUntracked;
 
 	private Preview preview;
 
@@ -93,10 +100,16 @@
 
 	static {
 		final PreviewResource project = new PreviewResource(
-				"Project", IResource.PROJECT, "master"); //$NON-NLS-1$1
+				"Project", IResource.PROJECT, "master", true, false); //$NON-NLS-1$1
 		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
-		children.add(new PreviewResource("folder", IResource.FOLDER, null)); //$NON-NLS-1$
-		children.add(new PreviewResource("file.txt", IResource.FILE, null)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"folder", IResource.FOLDER, null, true, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"file.txt", IResource.FILE, null, true, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"untracked.txt", IResource.FILE, null, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"ignored.txt", IResource.FILE, null, false, true)); //$NON-NLS-1$
 		project.children = children;
 		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
 	}
@@ -160,7 +173,7 @@ private Control createGeneralDecoratorPage(Composite parent) {
 		Composite composite = SWTUtils.createHVFillComposite(parent,
 				SWTUtils.MARGINS_DEFAULT);
 
-		showDirty = SWTUtils.createCheckBox(composite,
+		computeDeepDirtyState = SWTUtils.createCheckBox(composite,
 				UIText.DecoratorPreferencesPage_computeDeep);
 
 		return composite;
@@ -202,6 +215,11 @@ private Control createIconDecoratorPage(Composite parent) {
 		Composite imageGroup = SWTUtils.createHVFillComposite(parent,
 				SWTUtils.MARGINS_DEFAULT, 2);
 
+		showTracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowTracked);
+		showUntracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowUntracked);
+
 		return imageGroup;
 	}
 
@@ -241,6 +259,9 @@ public void handleEvent(Event event) {
 	private void initializeValues() {
 		final IPreferenceStore store = getPreferenceStore();
 
+		computeDeepDirtyState.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+
 		fileTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
 		folderTextFormat.setText(store
@@ -248,8 +269,20 @@ private void initializeValues() {
 		projectTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
 
-		showDirty.setSelection(store
-				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		showTracked.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON));
+		showUntracked.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
+
+		SelectionListener selectionListener = new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				fPreview.refresh();
+			}
+		};
+
+		computeDeepDirtyState.addSelectionListener(selectionListener);
+		showTracked.addSelectionListener(selectionListener);
+		showUntracked.addSelectionListener(selectionListener);
 
 		setValid(true);
 	}
@@ -287,6 +320,9 @@ public boolean performOk() {
 	 */
 	private boolean performOk(IPreferenceStore store) {
 
+		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY,
+				computeDeepDirtyState.getSelection());
+
 		store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				fileTextFormat.getText());
 		store.setValue(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
@@ -294,8 +330,10 @@ private boolean performOk(IPreferenceStore store) {
 		store.setValue(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
 				projectTextFormat.getText());
 
-		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY, showDirty
+		store.setValue(UIPreferences.DECORATOR_SHOW_TRACKED_ICON, showTracked
 				.getSelection());
+		store.setValue(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON,
+				showUntracked.getSelection());
 
 		return true;
 	}
@@ -308,6 +346,9 @@ protected void performDefaults() {
 		super.performDefaults();
 		IPreferenceStore store = getPreferenceStore();
 
+		computeDeepDirtyState.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+
 		fileTextFormat.setText(store
 				.getDefaultString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
 		folderTextFormat
@@ -317,8 +358,11 @@ protected void performDefaults() {
 				.setText(store
 						.getDefaultString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
 
-		showDirty.setSelection(store
-				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		showTracked.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON));
+		showUntracked
+				.setSelection(store
+						.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
 	}
 
 	/**
@@ -602,19 +646,26 @@ private PreviewDecoration getDecoration(Object element) {
 	}
 
 	private static class PreviewResource implements IDecoratableResource {
-		public final String name;
+		private final String name;
+
+		private final String branch;
 
-		public final String branch;
+		private final int type;
 
-		public final int type;
+		private Collection children;
 
-		public Collection children;
+		private boolean tracked;
 
-		public PreviewResource(String name, int type, String branch) {
+		private boolean ignored;
+
+		public PreviewResource(String name, int type, String branch,
+				boolean tracked, boolean ignored) {
 			this.name = name;
 			this.branch = branch;
 			this.type = type;
 			this.children = Collections.EMPTY_LIST;
+			this.tracked = tracked;
+			this.ignored = ignored;
 		}
 
 		public String getName() {
@@ -628,6 +679,14 @@ public int getType() {
 		public String getBranch() {
 			return branch;
 		}
+
+		public boolean isTracked() {
+			return tracked;
+		}
+
+		public boolean isIgnored() {
+			return ignored;
+		}
 	}
 
 	private class PreviewDecoration implements IDecoration {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index d050be9..9940177 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -369,4 +369,6 @@ DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and
 DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format:
 DecoratorPreferencesPage_textLabel=T&ext Decorations
 DecoratorPreferencesPage_iconLabel=&Icon Decorations
+DecoratorPreferencesPage_iconsShowTracked=Tracked resources
+DecoratorPreferencesPage_iconsShowUntracked=Untracked resources
 
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 09/12] Implement icon and text decorations of various resource states
  2009-02-11 18:40               ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
@ 2009-02-11 18:40                 ` Tor Arne Vestbø
  2009-02-11 18:40                   ` [EGIT PATCH v2 10/12] Don't decorate every single resource on repository change Tor Arne Vestbø
  2009-02-11 22:16                 ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Robin Rosenberg
  1 sibling, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

  - Dirty
  - Staged
  - Conflict
  - Assume valid

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.ui/icons/ovr/assume_valid.gif     |  Bin 0 -> 85 bytes
 org.spearce.egit.ui/icons/ovr/assumevalid.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/conflict.gif         |  Bin 64 -> 194 bytes
 org.spearce.egit.ui/icons/ovr/pending_add.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/pending_remove.gif   |  Bin 111 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/staged.gif           |  Bin 0 -> 114 bytes
 org.spearce.egit.ui/icons/ovr/staged_added.gif     |  Bin 0 -> 169 bytes
 org.spearce.egit.ui/icons/ovr/staged_removed.gif   |  Bin 0 -> 176 bytes
 .../egit/ui/PluginPreferenceInitializer.java       |    3 +
 .../src/org/spearce/egit/ui/UIIcons.java           |   15 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    6 +
 .../src/org/spearce/egit/ui/UIText.java            |   17 +-
 .../decorators/GitLightweightDecorator.java        |  299 +++++++++++++++++---
 .../internal/decorators/IDecoratableResource.java  |   46 +++
 .../preferences/GitDecoratorPreferencePage.java    |  152 +++++++++--
 .../src/org/spearce/egit/ui/uitext.properties      |   15 +-
 16 files changed, 477 insertions(+), 76 deletions(-)
 create mode 100644 org.spearce.egit.ui/icons/ovr/assume_valid.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/assumevalid.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/pending_add.gif
 delete mode 100644 org.spearce.egit.ui/icons/ovr/pending_remove.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged_added.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/staged_removed.gif

diff --git a/org.spearce.egit.ui/icons/ovr/assume_valid.gif b/org.spearce.egit.ui/icons/ovr/assume_valid.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b6d7167cb38b147aa0fcc115121f3166276f6688
GIT binary patch
literal 85
zcmZ?wbhEHb<Y3@pSj5Wk|Nno6&}sGa_b)s3!M=V!hzkaaKUo+V7+4u}Ks=CI24<;<
gU94*oY!sY|O?Vb;E(m>L;^NQ~(4hBvvkHSX0J)kQGXMYp

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/ovr/assumevalid.gif b/org.spearce.egit.ui/icons/ovr/assumevalid.gif
deleted file mode 100644
index c7262ed4e3f9437a51806f70fbc851e3a6f951d3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 64
zcmZ?wbhEHbWM|-D_{abP{~H+o{|7M?f3h$#FfcRdfH)v|1}4Ed_6CP$GF;`pupzry
Lj9oc|fx#L8z0(dE

diff --git a/org.spearce.egit.ui/icons/ovr/conflict.gif b/org.spearce.egit.ui/icons/ovr/conflict.gif
index b444be94a2d09561b212138b1514d5c07610cc07..fce456a49a0d6b19feaef4e1fb8615d8083c674c 100644
GIT binary patch
literal 194
zcmZ?wbhEHb<YwSvIKsfNSXBI}k=aul+s7VW3poWA@dz#iA)&<rB8vn>o~Y|S);7GW
zV|+_e`Ie;0T?OrX8YZiZHCF1XE!R_9uBNn9O6ro5(lQ147ctTQ!GHl3DE?$&WMGhD
z&;bd9>||iIU!ayIII}ZDEm63-?}`D3QE8g^;`e8!hE6+n_+o~G)HE-)gh(9`Kkf%@
d8(7v&JK{arFoJ8^;WC#KQvzQ|Td^=$0|4|&KIZ@c

literal 64
zcmZ?wbhEHbWM|-D_{hM}z`*dI0SXj<vM@3*Ffr(W_#k-(Cc!EF3&kHON*Qck_+rIG
KhIgS14AuZ~{td7I

diff --git a/org.spearce.egit.ui/icons/ovr/pending_add.gif b/org.spearce.egit.ui/icons/ovr/pending_add.gif
deleted file mode 100644
index f2306024b2872e50db4143a790bca93189ef8d5f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 64
zcmZ?wbhEHbWM|-Dn8?6je1?HxCd2>#{}~t<6o0a?iZC!S=m6OaAbAER!723%B?Oj5
R<v1{ie-xgi8_2+54FJ_W4pRUC

diff --git a/org.spearce.egit.ui/icons/ovr/pending_remove.gif b/org.spearce.egit.ui/icons/ovr/pending_remove.gif
deleted file mode 100644
index 4ecce038e66f904af8345f88c2c007706a4bc3d0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 111
zcmZ?wbhEHbWM|-D*v!E2;Qr&bwzj&uy5i#Ew6wIy$jG3eAU{99|Ns9pU;~OjSy)AY
sT6I8@ATt<PWC|uQx@~OiE8rFvPw;68lw@ATcdKBBRM#03Aw~vk0Ht*t9{>OV

diff --git a/org.spearce.egit.ui/icons/ovr/staged.gif b/org.spearce.egit.ui/icons/ovr/staged.gif
new file mode 100644
index 0000000000000000000000000000000000000000..dc0b8c01673b32fde0f9b11c8302ad1ca0a1e902
GIT binary patch
literal 114
zcmZ?wbhEHb<Y3@n*v!E2>(@D15q3p!E=^fpC2=k-SzZ$rK|38WV-+D!6DfNgu|P{%
zPh+VN3xogv|1&T!DE?$&WMJT9&;hc6CU7t?u*hFHX*qrG>5bPI{QDmDwl|kFq+1-m
RA=-DKlJ_A?g`5k6H30u&A6)<d

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/ovr/staged_added.gif b/org.spearce.egit.ui/icons/ovr/staged_added.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c65d16a191f259e0feb55c9244e7545c9063f2ee
GIT binary patch
literal 169
zcmZ?wbhEHb<Y3@nI3mv=<D+lc6C~rUFXwNhSM8ymWTTpFZ&2l?k?oi~MbDtt!(w8P
zX13k6n|3NGHU_nBa)HL3YfROWEiL<k^{d^EU9;|4ZK_}8(Y4mhqBp>NLa2<7LGmO+
zwPfqgH75W6|5yCU!pOiN&!EEq1Ry&ZSk)e=_dO9$3EpXP>xYb&Yrys66%*4FPo$Ud
U`8i&abQEg7#HJw7%D`X^09=zc+W-In

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/icons/ovr/staged_removed.gif b/org.spearce.egit.ui/icons/ovr/staged_removed.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8aaadfb9b9300594a3448986650b82841398287c
GIT binary patch
literal 176
zcmZ?wbhEHb<Y3@nI3mX2@9!TQ8ygf96c`wooSa-&SLf&FS5i_E8XDTx)|Q{29~>Oq
z-rk;)l2TY$7#SHE5D<`;mlqcoS6p2D;Qr&HqN4x*|1;nQia%Kx85qPEbU;c#b~3Q)
zE>P`DcJ&Dn4ViXC=E@Y0u&)L|A2*0x3h8BCs@CD4#4P3#=;_K}%6y8ku!Vua8UVS)
BGb{iA

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
index 7465444..a3196f4 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
@@ -44,6 +44,9 @@ public void initializeDefaultPreferences() {
 				UIText.DecoratorPreferencesPage_projectFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_SHOW_TRACKED_ICON, true);
 		prefs.setDefault(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_STAGED_ICON, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON, true);
 
 		w = new int[] { 500, 500 };
 		UIPreferences.setDefault(prefs,
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
index 4c0d189..952816c 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
@@ -18,11 +18,15 @@
  * Icons for the the Eclipse plugin. Mostly decorations.
  */
 public class UIIcons {
+
+	/** Decoration for resource in the index but not yet committed. */
+	public static final ImageDescriptor OVR_STAGED;
+
 	/** Decoration for resource added to index but not yet committed. */
-	public static final ImageDescriptor OVR_PENDING_ADD;
+	public static final ImageDescriptor OVR_STAGED_ADD;
 
 	/** Decoration for resource removed from the index but not commit. */
-	public static final ImageDescriptor OVR_PENDING_REMOVE;
+	public static final ImageDescriptor OVR_STAGED_REMOVE;
 
 	/** Decoration for resource not being tracked by Git */
 	public static final ImageDescriptor OVR_UNTRACKED;
@@ -84,11 +88,12 @@
 
 	static {
 		base = init();
-		OVR_PENDING_ADD = map("ovr/pending_add.gif");
-		OVR_PENDING_REMOVE = map("ovr/pending_remove.gif");
+		OVR_STAGED = map("ovr/staged.gif");
+		OVR_STAGED_ADD = map("ovr/staged_added.gif");
+		OVR_STAGED_REMOVE = map("ovr/staged_removed.gif");
 		OVR_UNTRACKED = map("ovr/untracked.gif");
 		OVR_CONFLICT = map("ovr/conflict.gif");
-		OVR_ASSUMEVALID = map("ovr/assumevalid.gif");
+		OVR_ASSUMEVALID = map("ovr/assume_valid.gif");
 		ELCL16_FIND = map("elcl16/find.gif");
 		ELCL16_NEXT = map("elcl16/next.gif");
 		ELCL16_PREVIOUS = map("elcl16/previous.gif");
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
index 7916cea..e812716 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
@@ -64,6 +64,12 @@
 	public final static String DECORATOR_SHOW_TRACKED_ICON = "decorator_show_tracked_icon";
 	/** */
 	public final static String DECORATOR_SHOW_UNTRACKED_ICON = "decorator_show_untracked_icon";
+	/** */
+	public final static String DECORATOR_SHOW_STAGED_ICON = "decorator_show_staged_icon";
+	/** */
+	public final static String DECORATOR_SHOW_CONFLICTS_ICON = "decorator_show_conflicts_icon";
+	/** */
+	public final static String DECORATOR_SHOW_ASSUME_VALID_ICON = "decorator_show_assume_valid_icon";
 
 	/**
 	 * Get the preference values associated with a fixed integer array.
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 60e4eaa..7e26337 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -968,12 +968,18 @@
 	public static String DecoratorPreferencesPage_generalTabFolder;
 
 	/** */
-	public static String DecoratorPreferencesPage_nameResourceVariable;
+	public static String DecoratorPreferencesPage_bindingResourceName;
 
 	/** */
 	public static String DecoratorPreferencesPage_bindingBranchName;
 
 	/** */
+	public static String DecoratorPreferencesPage_bindingDirtyFlag;
+
+	/** */
+	public static String DecoratorPreferencesPage_bindingStagedFlag;
+
+	/** */
 	public static String DecoratorPreferencesPage_selectFormats;
 
 	/** */
@@ -994,6 +1000,15 @@
 	/** */
 	public static String DecoratorPreferencesPage_iconsShowUntracked;
 
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowStaged;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowConflicts;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowAssumeValid;
+
 	static {
 		initializeMessages(UIText.class.getPackage().getName() + ".uitext",
 				UIText.class);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index b20070a..c23ce24 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -60,9 +60,12 @@
 import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
+import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
 import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheEntry;
 import org.spearce.jgit.dircache.DirCacheIterator;
 import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
 import org.spearce.jgit.lib.IndexChangedEvent;
 import org.spearce.jgit.lib.ObjectId;
 import org.spearce.jgit.lib.RefsChangedEvent;
@@ -200,6 +203,20 @@ public void decorate(Object element, IDecoration decoration) {
 
 		private boolean ignored = false;
 
+		private boolean dirty = false;
+
+		private boolean conflicts = false;
+
+		private boolean assumeValid = false;
+
+		private Staged staged = Staged.NOT_STAGED;
+
+		static final int T_HEAD = 0;
+
+		static final int T_INDEX = 1;
+
+		static final int T_WORKSPACE = 2;
+
 		public DecoratableResourceAdapter(IResource resourceToWrap)
 				throws IOException {
 			resource = resourceToWrap;
@@ -207,59 +224,162 @@ public DecoratableResourceAdapter(IResource resourceToWrap)
 			repository = mapping.getRepository();
 			headId = repository.resolve(Constants.HEAD);
 
-			initializeValues();
+			switch (resource.getType()) {
+			case IResource.FILE:
+				extractFileProperties();
+				break;
+			case IResource.FOLDER:
+				extractContainerProperties();
+				break;
+			case IResource.PROJECT:
+				extractProjectProperties();
+				break;
+			}
 		}
 
-		/**
-		 * Initialize the various values that are used for making decoration
-		 * decisions later on.
-		 * 
-		 * We might as well pre-load these now, instead of using lazy
-		 * initialization, because they are all read by the decorator when
-		 * building variable bindings and computing the preferred overlay.
-		 * 
-		 * @throws IOException
-		 */
-		private void initializeValues() throws IOException {
-
-			// Resolve current branch
-			branch = repository.getBranch();
+		private void extractFileProperties() throws IOException {
+			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
+			if (treeWalk == null)
+				return;
 
-			// Resolve tracked state
-			if (getType() == IResource.PROJECT) {
+			if (treeWalk.next())
 				tracked = true;
+			else
+				return;
+
+			// TODO: Also read ignores from .git/info/excludes et al.
+			if (Team.isIgnoredHint(resource)) {
+				ignored = true;
+				return;
+			}
+
+			final DirCacheIterator indexIterator = treeWalk.getTree(T_INDEX,
+					DirCacheIterator.class);
+			final DirCacheEntry indexEntry = indexIterator != null ? indexIterator
+					.getDirCacheEntry()
+					: null;
+
+			if (indexEntry == null) {
+				staged = Staged.REMOVED;
 			} else {
-				final TreeWalk treeWalk = new TreeWalk(repository);
-
-				Set<String> repositoryPaths = Collections.singleton(mapping
-						.getRepoRelativePath(resource));
-				if (!(repositoryPaths.isEmpty() || repositoryPaths.contains(""))) {
-					treeWalk.setFilter(PathFilterGroup
-							.createFromStrings(repositoryPaths));
-					treeWalk.setRecursive(treeWalk.getFilter()
-							.shouldBeRecursive());
-					treeWalk.reset();
-
-					if (headId != null)
-						treeWalk.addTree(new RevWalk(repository)
-								.parseTree(headId));
-					else
-						treeWalk.addTree(new EmptyTreeIterator());
+				if (indexEntry.isAssumeValid()) {
+					dirty = false;
+					assumeValid = true;
+				} else if (indexEntry.getStage() > 0) {
+					conflicts = true;
+				} else if (treeWalk.getRawMode(T_HEAD) == FileMode.MISSING
+						.getBits()) {
+					staged = Staged.ADDED;
+				} else {
+					long indexEntryLastModified = indexEntry.getLastModified();
+					long resourceLastModified = resource.getLocalTimeStamp();
+
+					// C-Git under Windows stores timestamps with 1-seconds
+					// resolution, so we need to check to see if this is the
+					// case here, and possibly fix the timestamp of the resource
+					// to match the resolution of the index.
+					if (indexEntryLastModified % 1000 == 0) {
+						resourceLastModified -= resourceLastModified % 1000;
+					}
+
+					if (resourceLastModified != indexEntryLastModified) {
+						// TODO: Consider doing a content check here, to rule
+						// out false positives, as we might get mismatch between
+						// timestamps, even if the content is the same
+						dirty = true;
+					}
 
-					treeWalk.addTree(new DirCacheIterator(DirCache
-							.read(repository)));
-					if (treeWalk.next()) {
-						tracked = true;
+					if (treeWalk.getRawMode(T_HEAD) != treeWalk
+							.getRawMode(T_INDEX)
+							|| !treeWalk.idEqual(T_HEAD, T_INDEX)) {
+						staged = Staged.MODIFIED;
 					}
 				}
 			}
 
-			// Resolve ignored state (currently only reads the global Eclipse
-			// ignores)
+		}
+
+		private void extractContainerProperties() throws IOException {
+			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
+			if (treeWalk == null)
+				return;
+
+			if (treeWalk.next())
+				tracked = true;
+			else
+				return;
+
 			// TODO: Also read ignores from .git/info/excludes et al.
 			if (Team.isIgnoredHint(resource)) {
 				ignored = true;
+				return;
 			}
+
+			// TODO: Compute dirty state for folder, using ContainerTreeIterator
+			// and ContainerDiffFilter
+
+		}
+
+		private void extractProjectProperties() throws IOException {
+			branch = repository.getBranch();
+			tracked = true;
+
+			// TODO: Compute dirty state for folder, using ContainerTreeIterator
+			// and ContainerDiffFilter
+
+		}
+
+		/**
+		 * Adds a filter to the specified tree walk limiting the results to only
+		 * those matching the resource specified by
+		 * <code>resourceToFilterBy</code>
+		 * <p>
+		 * If the resource does not exists in the current repository, or it has
+		 * an empty path (it is the project itself), the filter is not added,
+		 * and the method returns <code>null</code>.
+		 * 
+		 * @param treeWalk
+		 *            the tree walk to add the filter to
+		 * @param resourceToFilterBy
+		 *            the resource to filter by
+		 * 
+		 * @return <code>true</code> if the filter could be added,
+		 *         <code>false</code> otherwise
+		 */
+		private boolean addResourceFilter(final TreeWalk treeWalk,
+				final IResource resourceToFilterBy) {
+			Set<String> repositoryPaths = Collections.singleton(mapping
+					.getRepoRelativePath(resourceToFilterBy));
+			if (repositoryPaths.isEmpty() || repositoryPaths.contains(""))
+				return false;
+
+			treeWalk.setFilter(PathFilterGroup
+					.createFromStrings(repositoryPaths));
+			return true;
+		}
+
+		/**
+		 * Helper method to create a new tree walk between HEAD and the index.
+		 * 
+		 * @return the created tree walk, or null if it could not be created
+		 * @throws IOException
+		 *             if there were errors when creating the tree walk
+		 */
+		private TreeWalk createHeadVsIndexTreeWalk() throws IOException {
+			final TreeWalk treeWalk = new TreeWalk(repository);
+			if (!addResourceFilter(treeWalk, resource))
+				return null;
+
+			treeWalk.setRecursive(treeWalk.getFilter().shouldBeRecursive());
+			treeWalk.reset();
+
+			if (headId != null)
+				treeWalk.addTree(new RevWalk(repository).parseTree(headId));
+			else
+				treeWalk.addTree(new EmptyTreeIterator());
+
+			treeWalk.addTree(new DirCacheIterator(DirCache.read(repository)));
+			return treeWalk;
 		}
 
 		public String getName() {
@@ -281,6 +401,22 @@ public boolean isTracked() {
 		public boolean isIgnored() {
 			return ignored;
 		}
+
+		public boolean isDirty() {
+			return dirty;
+		}
+
+		public Staged staged() {
+			return staged;
+		}
+
+		public boolean hasConflicts() {
+			return conflicts;
+		}
+
+		public boolean isAssumeValid() {
+			return assumeValid;
+		}
 	}
 
 	/**
@@ -298,6 +434,12 @@ public boolean isIgnored() {
 		/** */
 		public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
 
+		/** */
+		public static final String BINDING_DIRTY_FLAG = "dirty"; //$NON-NLS-1$
+
+		/** */
+		public static final String BINDING_STAGED_FLAG = "staged"; //$NON-NLS-1$
+
 		private IPreferenceStore store;
 
 		/**
@@ -325,10 +467,26 @@ public ImageData getImageData() {
 
 		private static ImageDescriptor untrackedImage;
 
+		private static ImageDescriptor stagedImage;
+
+		private static ImageDescriptor stagedAddedImage;
+
+		private static ImageDescriptor stagedRemovedImage;
+
+		private static ImageDescriptor conflictImage;
+
+		private static ImageDescriptor assumeValidImage;
+
 		static {
 			trackedImage = new CachedImageDescriptor(TeamImages
 					.getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR));
 			untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED);
+			stagedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED);
+			stagedAddedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED_ADD);
+			stagedRemovedImage = new CachedImageDescriptor(
+					UIIcons.OVR_STAGED_REMOVE);
+			conflictImage = new CachedImageDescriptor(UIIcons.OVR_CONFLICT);
+			assumeValidImage = new CachedImageDescriptor(UIIcons.OVR_ASSUMEVALID);
 		}
 
 		/**
@@ -354,6 +512,9 @@ public DecorationHelper(IPreferenceStore preferencesStore) {
 		 */
 		public void decorate(IDecoration decoration,
 				IDecoratableResource resource) {
+			if (resource.isIgnored())
+				return;
+
 			decorateText(decoration, resource);
 			decorateIcons(decoration, resource);
 		}
@@ -379,22 +540,49 @@ private void decorateText(IDecoration decoration,
 			Map<String, String> bindings = new HashMap<String, String>();
 			bindings.put(BINDING_RESOURCE_NAME, resource.getName());
 			bindings.put(BINDING_BRANCH_NAME, resource.getBranch());
+			bindings.put(BINDING_DIRTY_FLAG, resource.isDirty() ? ">" : null);
+			bindings.put(BINDING_STAGED_FLAG,
+					resource.staged() != Staged.NOT_STAGED ? "*" : null);
 
 			decorate(decoration, format, bindings);
 		}
 
 		private void decorateIcons(IDecoration decoration,
 				IDecoratableResource resource) {
-			if (resource.isIgnored())
-				return;
+			ImageDescriptor overlay = null;
 
 			if (resource.isTracked()) {
 				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON))
-					decoration.addOverlay(trackedImage);
-			} else if (store
-					.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
-				decoration.addOverlay(untrackedImage);
+					overlay = trackedImage;
+
+				if (store
+						.getBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON)
+						&& resource.isAssumeValid())
+					overlay = assumeValidImage;
+
+				// Staged overrides tracked
+				Staged staged = resource.staged();
+				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON)
+						&& staged != Staged.NOT_STAGED) {
+					if (staged == Staged.ADDED)
+						overlay = stagedAddedImage;
+					else if (staged == Staged.REMOVED)
+						overlay = stagedRemovedImage;
+					else
+						overlay = stagedImage;
+				}
+
+				// Conflicts override everything
+				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON)
+						&& resource.hasConflicts())
+					overlay = conflictImage;
+
+			} else if (store.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
+				overlay = untrackedImage;
 			}
+
+			// Overlays can only be added once, so do it at the end
+			decoration.addOverlay(overlay);
 		}
 
 		/**
@@ -411,7 +599,7 @@ private void decorateIcons(IDecoration decoration,
 		 *            values
 		 */
 		public static void decorate(IDecoration decoration, String format,
-				Map bindings) {
+				Map<String, String> bindings) {
 			StringBuffer prefix = new StringBuffer();
 			StringBuffer suffix = new StringBuffer();
 			StringBuffer output = prefix;
@@ -426,6 +614,15 @@ public static void decorate(IDecoration decoration, String format,
 						String key = format.substring(end + 1, start);
 						String s;
 
+						// Allow users to override the binding
+						if (key.indexOf(':') > -1) {
+							String[] keyAndBinding = key.split(":", 2);
+							key = keyAndBinding[0];
+							if (keyAndBinding.length > 1
+									&& bindings.get(key) != null)
+								bindings.put(key, keyAndBinding[1]);
+						}
+
 						// We use the BINDING_RESOURCE_NAME key to determine if
 						// we are doing the prefix or suffix. The name isn't
 						// actually part of either.
@@ -433,7 +630,7 @@ public static void decorate(IDecoration decoration, String format,
 							output = suffix;
 							s = null;
 						} else {
-							s = (String) bindings.get(key);
+							s = bindings.get(key);
 						}
 
 						if (s != null) {
@@ -522,6 +719,14 @@ public void resourceChanged(IResourceChangeEvent event) {
 				public boolean visit(IResourceDelta delta) throws CoreException {
 					final IResource resource = delta.getResource();
 
+					// If the resource is not part of a project under Git
+					// revision control
+					final RepositoryMapping mapping = RepositoryMapping
+							.getMapping(resource);
+					if (mapping == null) {
+						// Ignore the change
+						return true;
+					}
 					if (resource.getType() == IResource.ROOT) {
 						// Continue with the delta
 						return true;
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
index f144214..b864a10 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
@@ -16,6 +16,22 @@
 public interface IDecoratableResource {
 
 	/**
+	 * Set of possible staging states for a resource
+	 */
+	public enum Staged {
+		/** Represents a resource that is not staged */
+		NOT_STAGED,
+		/** Represents a resource that has been modified */
+		MODIFIED,
+		/** Represents a resource that is added to Git */
+		ADDED,
+		/** Represents a resource that is removed from Git */
+		REMOVED,
+		/** Represents a resource that has been renamed */
+		RENAMED
+	}
+
+	/**
 	 * Gets the type of the resource as defined by {@link IResource}
 	 * 
 	 * @return the type of the resource
@@ -51,4 +67,34 @@
 	 * @return whether or not the resource is ignored
 	 */
 	boolean isIgnored();
+
+	/**
+	 * Returns whether or not the resource has changes that are not staged
+	 * 
+	 * @return whether or not the resource is dirty
+	 */
+	boolean isDirty();
+
+	/**
+	 * Returns the staged state of the resource
+	 * 
+	 * The set of allowed values are defined by the <code>Staged</code> enum
+	 * 
+	 * @return the staged state of the resource
+	 */
+	Staged staged();
+
+	/**
+	 * Returns whether or not the resource has merge conflicts
+	 * 
+	 * @return whether or not the resource has merge conflicts
+	 */
+	boolean hasConflicts();
+
+	/**
+	 * Returns whether or not the resource is assumed valid
+	 * 
+	 * @return whether or not the resource is assumed valid
+	 */
+	boolean isAssumeValid();
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
index 7b637e3..f72ceb7 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -55,6 +55,7 @@
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.TabFolder;
 import org.eclipse.swt.widgets.TabItem;
@@ -71,8 +72,9 @@
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
 import org.spearce.egit.ui.internal.SWTUtils;
-import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator.DecorationHelper;
 import org.spearce.egit.ui.internal.decorators.IDecoratableResource;
+import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator.DecorationHelper;
+import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
 
 /**
  * Preference page for customizing Git label decorations
@@ -94,22 +96,43 @@
 
 	private Preview preview;
 
+	private Button showStaged;
+
+	private Button showConflicts;
+
+	private Button showAssumeValid;
+
 	private static final Collection PREVIEW_FILESYSTEM_ROOT;
 
 	private static IPropertyChangeListener themeListener;
 
 	static {
 		final PreviewResource project = new PreviewResource(
-				"Project", IResource.PROJECT, "master", true, false); //$NON-NLS-1$1
+				"Project", IResource.PROJECT, "master", true, false, false, Staged.NOT_STAGED, false, false); //$NON-NLS-1$1
 		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
+
+		children.add(new PreviewResource(
+						"folder", IResource.FOLDER, null, true, false, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"tracked.txt", IResource.FILE, null, true, false, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
-				"folder", IResource.FOLDER, null, true, false)); //$NON-NLS-1$
+						"untracked.txt", IResource.FILE, null, false, false, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
-				"file.txt", IResource.FILE, null, true, false)); //$NON-NLS-1$
+						"ignored.txt", IResource.FILE, null, false, true, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
-				"untracked.txt", IResource.FILE, null, false, false)); //$NON-NLS-1$
+						"dirty.txt", IResource.FILE, null, true, false, true, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
-				"ignored.txt", IResource.FILE, null, false, true)); //$NON-NLS-1$
+						"staged.txt", IResource.FILE, null, true, false, false, Staged.MODIFIED, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"partially-staged.txt", IResource.FILE, null, true, false, true, Staged.MODIFIED, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"added.txt", IResource.FILE, null, true, false, false, Staged.ADDED, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"removed.txt", IResource.FILE, null, true, false, false, Staged.REMOVED, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"conflict.txt", IResource.FILE, null, true, false, true, Staged.NOT_STAGED, true, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"assume-valid.txt", IResource.FILE, null, true, false, false, Staged.NOT_STAGED, false, true)); //$NON-NLS-1$
 		project.children = children;
 		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
 	}
@@ -190,22 +213,29 @@ private Control createTextDecoratorPage(Composite parent) {
 		Composite fileTextGroup = SWTUtils.createHVFillComposite(parent,
 				SWTUtils.MARGINS_DEFAULT, 3);
 
+		int labelWidth = convertWidthInCharsToPixels(Math.max(
+				UIText.DecoratorPreferencesPage_fileFormatLabel.length(),
+				Math.max(UIText.DecoratorPreferencesPage_folderFormatLabel
+						.length(),
+						UIText.DecoratorPreferencesPage_projectFormatLabel
+								.length())));
+
 		TextPair format = createFormatEditorControl(fileTextGroup,
 				UIText.DecoratorPreferencesPage_fileFormatLabel,
 				UIText.DecoratorPreferencesPage_addVariablesAction,
-				getFileBindingDescriptions());
+				getFileBindingDescriptions(), labelWidth);
 		fileTextFormat = format.t1;
 
 		format = createFormatEditorControl(fileTextGroup,
 				UIText.DecoratorPreferencesPage_folderFormatLabel,
 				UIText.DecoratorPreferencesPage_addVariablesAction,
-				getFolderBindingDescriptions());
+				getFolderBindingDescriptions(), labelWidth);
 		folderTextFormat = format.t1;
 
 		format = createFormatEditorControl(fileTextGroup,
 				UIText.DecoratorPreferencesPage_projectFormatLabel,
 				UIText.DecoratorPreferencesPage_addVariablesAction,
-				getProjectBindingDescriptions());
+				getProjectBindingDescriptions(), labelWidth);
 		projectTextFormat = format.t1;
 
 		return fileTextGroup;
@@ -219,17 +249,29 @@ private Control createIconDecoratorPage(Composite parent) {
 				UIText.DecoratorPreferencesPage_iconsShowTracked);
 		showUntracked = SWTUtils.createCheckBox(imageGroup,
 				UIText.DecoratorPreferencesPage_iconsShowUntracked);
+		showStaged = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowStaged);
+		showConflicts = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowConflicts);
+		showAssumeValid = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowAssumeValid);
 
 		return imageGroup;
 	}
 
 	private TextPair createFormatEditorControl(Composite composite,
-			String title, String buttonText, final Map supportedBindings) {
+			String title, String buttonText, final Map supportedBindings,
+			int labelWidth) {
 
-		SWTUtils.createLabel(composite, title);
+		Label label = SWTUtils.createLabel(composite, title);
+		GridData labelGridData = new GridData();
+		labelGridData.widthHint = labelWidth;
+		label.setLayoutData(labelGridData);
 
 		Text format = new Text(composite, SWT.BORDER);
-		format.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+		GridData textGridData = new GridData(GridData.FILL_HORIZONTAL);
+		textGridData.widthHint = 200;
+		format.setLayoutData(textGridData);
 		format.addModifyListener(new ModifyListener() {
 			public void modifyText(ModifyEvent e) {
 				updatePreview();
@@ -273,16 +315,25 @@ private void initializeValues() {
 				.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON));
 		showUntracked.setSelection(store
 				.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
+		showStaged.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON));
+		showConflicts.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON));
+		showAssumeValid.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON));
 
 		SelectionListener selectionListener = new SelectionAdapter() {
 			public void widgetSelected(SelectionEvent e) {
-				fPreview.refresh();
+				preview.refresh();
 			}
 		};
 
 		computeDeepDirtyState.addSelectionListener(selectionListener);
 		showTracked.addSelectionListener(selectionListener);
 		showUntracked.addSelectionListener(selectionListener);
+		showStaged.addSelectionListener(selectionListener);
+		showConflicts.addSelectionListener(selectionListener);
+		showAssumeValid.addSelectionListener(selectionListener);
 
 		setValid(true);
 	}
@@ -334,6 +385,12 @@ private boolean performOk(IPreferenceStore store) {
 				.getSelection());
 		store.setValue(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON,
 				showUntracked.getSelection());
+		store.setValue(UIPreferences.DECORATOR_SHOW_STAGED_ICON, showStaged
+				.getSelection());
+		store.setValue(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON,
+				showConflicts.getSelection());
+		store.setValue(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON,
+				showAssumeValid.getSelection());
 
 		return true;
 	}
@@ -363,6 +420,14 @@ protected void performDefaults() {
 		showUntracked
 				.setSelection(store
 						.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
+		showStaged.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON));
+		showConflicts
+				.setSelection(store
+						.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON));
+		showAssumeValid
+				.setSelection(store
+						.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON));
 	}
 
 	/**
@@ -473,7 +538,11 @@ TextPair(Text t1, Text t2) {
 	private Map getFileBindingDescriptions() {
 		Map<String, String> bindings = new HashMap<String, String>();
 		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
-				UIText.DecoratorPreferencesPage_nameResourceVariable);
+				UIText.DecoratorPreferencesPage_bindingResourceName);
+		bindings.put(DecorationHelper.BINDING_DIRTY_FLAG,
+				UIText.DecoratorPreferencesPage_bindingDirtyFlag);
+		bindings.put(DecorationHelper.BINDING_STAGED_FLAG,
+				UIText.DecoratorPreferencesPage_bindingStagedFlag);
 		return bindings;
 	}
 
@@ -486,7 +555,11 @@ private Map getFileBindingDescriptions() {
 	private Map getFolderBindingDescriptions() {
 		Map<String, String> bindings = new HashMap<String, String>();
 		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
-				UIText.DecoratorPreferencesPage_nameResourceVariable);
+				UIText.DecoratorPreferencesPage_bindingResourceName);
+		bindings.put(DecorationHelper.BINDING_DIRTY_FLAG,
+				UIText.DecoratorPreferencesPage_bindingDirtyFlag);
+		bindings.put(DecorationHelper.BINDING_STAGED_FLAG,
+				UIText.DecoratorPreferencesPage_bindingStagedFlag);
 		return bindings;
 	}
 
@@ -499,7 +572,11 @@ private Map getFolderBindingDescriptions() {
 	private Map getProjectBindingDescriptions() {
 		Map<String, String> bindings = new HashMap<String, String>();
 		bindings.put(DecorationHelper.BINDING_RESOURCE_NAME,
-				UIText.DecoratorPreferencesPage_nameResourceVariable);
+				UIText.DecoratorPreferencesPage_bindingResourceName);
+		bindings.put(DecorationHelper.BINDING_DIRTY_FLAG,
+				UIText.DecoratorPreferencesPage_bindingDirtyFlag);
+		bindings.put(DecorationHelper.BINDING_STAGED_FLAG,
+				UIText.DecoratorPreferencesPage_bindingStagedFlag);
 		bindings.put(DecorationHelper.BINDING_BRANCH_NAME,
 				UIText.DecoratorPreferencesPage_bindingBranchName);
 		return bindings;
@@ -658,14 +735,28 @@ private PreviewDecoration getDecoration(Object element) {
 
 		private boolean ignored;
 
+		private boolean dirty;
+
+		private boolean conflicts;
+
+		private Staged staged;
+
+		private boolean assumeValid;
+
 		public PreviewResource(String name, int type, String branch,
-				boolean tracked, boolean ignored) {
+				boolean tracked, boolean ignored, boolean dirty, Staged staged,
+				boolean conflicts, boolean assumeValid) {
+
 			this.name = name;
 			this.branch = branch;
 			this.type = type;
 			this.children = Collections.EMPTY_LIST;
 			this.tracked = tracked;
 			this.ignored = ignored;
+			this.dirty = dirty;
+			this.staged = staged;
+			this.conflicts = conflicts;
+			this.assumeValid = assumeValid; 
 		}
 
 		public String getName() {
@@ -687,6 +778,22 @@ public boolean isTracked() {
 		public boolean isIgnored() {
 			return ignored;
 		}
+
+		public boolean isDirty() {
+			return dirty;
+		}
+
+		public Staged staged() {
+			return staged;
+		}
+
+		public boolean hasConflicts() {
+			return conflicts;
+		}
+
+		public boolean isAssumeValid() {
+			return assumeValid;
+		}
 	}
 
 	private class PreviewDecoration implements IDecoration {
@@ -703,12 +810,19 @@ public boolean isIgnored() {
 
 		private Color foregroundColor;
 
+		/**
+		 * Adds an icon overlay to the decoration
+		 * <p>
+		 * Copies the behavior of <code>DecorationBuilder</code> of only
+		 * allowing the overlay to be set once.
+		 */
 		public void addOverlay(ImageDescriptor overlayImage) {
-			overlay = overlayImage;
+			if (overlay == null)
+				overlay = overlayImage;
 		}
 
 		public void addOverlay(ImageDescriptor overlayImage, int quadrant) {
-			overlay = overlayImage;
+			addOverlay(overlayImage);
 		}
 
 		public void addPrefix(String prefix) {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index 9940177..e9a2321 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -358,17 +358,24 @@ DecoratorPreferencesPage_preview=Preview:
 DecoratorPreferencesPage_fileFormatLabel=&Files:
 DecoratorPreferencesPage_folderFormatLabel=F&olders:
 DecoratorPreferencesPage_projectFormatLabel=&Projects:
-DecoratorPreferencesPage_fileFormatDefault={name}
-DecoratorPreferencesPage_folderFormatDefault={name}
-DecoratorPreferencesPage_projectFormatDefault={name} [{branch}]
+DecoratorPreferencesPage_fileFormatDefault={dirty:>} {name}
+DecoratorPreferencesPage_folderFormatDefault={dirty:>} {name}
+DecoratorPreferencesPage_projectFormatDefault={dirty:>} {name} [{branch}]
 DecoratorPreferencesPage_labelDecorationsLink=See <a>''{0}''</a> to enable or disable Git decorations.
 DecoratorPreferencesPage_generalTabFolder=&General
-DecoratorPreferencesPage_nameResourceVariable=name of the resource being decorated
+DecoratorPreferencesPage_bindingResourceName=name of the resource being decorated
 DecoratorPreferencesPage_bindingBranchName=current branch of the project
+DecoratorPreferencesPage_bindingDirtyFlag=flag indicating whether or not the resource is dirty
+DecoratorPreferencesPage_bindingStagedFlag=flag indicating whether or not the resource is staged
 DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and project text labels:
 DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format:
 DecoratorPreferencesPage_textLabel=T&ext Decorations
 DecoratorPreferencesPage_iconLabel=&Icon Decorations
 DecoratorPreferencesPage_iconsShowTracked=Tracked resources
 DecoratorPreferencesPage_iconsShowUntracked=Untracked resources
+DecoratorPreferencesPage_iconsShowStaged=Staged resources
+DecoratorPreferencesPage_iconsShowConflicts=Conflicting resources
+DecoratorPreferencesPage_iconsShowAssumeValid=Assumed unchanged resources
+
+Decorator_exceptionMessage=Errors occurred while applying Git decorations to resources.
 
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 10/12] Don't decorate every single resource on repository change
  2009-02-11 18:40                 ` [EGIT PATCH v2 09/12] Implement icon and text decorations of various resource states Tor Arne Vestbø
@ 2009-02-11 18:40                   ` Tor Arne Vestbø
  2009-02-11 18:40                     ` [EGIT PATCH v2 11/12] Expose the underlying resource entries in ContainerTreeIterator Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Not all resources have corresponding labels that are visible,
so by using LabelProviderChangedEvent() we ensure that only
the visible labels are refreshed.

The downside is that we lose project precition, so all
projects are included, but only visible labels in those
projects are re-decorated, so it is OK for now.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../decorators/GitLightweightDecorator.java        |   28 +++-----------------
 1 files changed, 4 insertions(+), 24 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index c23ce24..1e95369 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -14,11 +14,9 @@
 package org.spearce.egit.ui.internal.decorators;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -28,7 +26,6 @@
 import org.eclipse.core.resources.IResourceChangeListener;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.resources.IResourceDeltaVisitor;
-import org.eclipse.core.resources.IResourceVisitor;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.resources.mapping.ResourceMapping;
 import org.eclipse.core.runtime.CoreException;
@@ -697,7 +694,7 @@ public void propertyChange(PropertyChangeEvent event) {
 		if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED)
 				|| prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED)
 				|| prop.equals(Activator.DECORATORS_CHANGED)) {
-			postLabelEvent(new LabelProviderChangedEvent(this, null /* all */));
+			postLabelEvent(new LabelProviderChangedEvent(this));
 		}
 	}
 
@@ -824,29 +821,12 @@ public void refsChanged(RefsChangedEvent e) {
 	 * Callback for RepositoryChangeListener events, as well as
 	 * RepositoryListener events via repositoryChanged()
 	 * 
-	 * We resolve the project and schedule a refresh of each resource in the
-	 * project.
-	 * 
 	 * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping)
 	 */
 	public void repositoryChanged(RepositoryMapping mapping) {
-		final IProject project = mapping.getContainer().getProject();
-		if (project == null)
-			return;
-
-		final List<IResource> resources = new ArrayList<IResource>();
-		try {
-			project.accept(new IResourceVisitor() {
-				public boolean visit(IResource resource) {
-					resources.add(resource);
-					return true;
-				}
-			});
-			postLabelEvent(new LabelProviderChangedEvent(this, resources
-					.toArray()));
-		} catch (final CoreException e) {
-			handleException(project, e);
-		}
+		// Until we find a way to refresh visible labels within a project
+		// we have to use this blanket refresh that includes all projects.
+		postLabelEvent(new LabelProviderChangedEvent(this));
 	}
 
 	// -------- Helper methods --------
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 11/12] Expose the underlying resource entries in ContainerTreeIterator
  2009-02-11 18:40                   ` [EGIT PATCH v2 10/12] Don't decorate every single resource on repository change Tor Arne Vestbø
@ 2009-02-11 18:40                     ` Tor Arne Vestbø
  2009-02-11 18:40                       ` [EGIT PATCH v2 12/12] Implement label decorations for folders and projects Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

This is needed if interacting with any of the Eclipse platform
methods that take IResources as arguments.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../spearce/egit/core/ContainerTreeIterator.java   |   23 +++++++++++++++++++-
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    9 +++++++
 2 files changed, 31 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java b/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
index 6d6b72e..2403252 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
@@ -106,6 +106,15 @@ public AbstractTreeIterator createSubtreeIterator(final Repository db)
 					Constants.TYPE_TREE);
 	}
 
+	/**
+	 * Get the ResourceEntry for the current entry.
+	 * 
+	 * @return the current entry
+	 */
+	public ResourceEntry getResourceEntry() {
+		return (ResourceEntry) current();
+	}
+
 	private Entry[] entries() {
 		final IResource[] all;
 		try {
@@ -120,7 +129,10 @@ public AbstractTreeIterator createSubtreeIterator(final Repository db)
 		return r;
 	}
 
-	static class ResourceEntry extends Entry {
+	/**
+	 * Wrapper for a resource in the Eclipse workspace
+	 */
+	static public class ResourceEntry extends Entry {
 		final IResource rsrc;
 
 		private final FileMode mode;
@@ -195,6 +207,15 @@ public InputStream openInputStream() throws IOException {
 			throw new IOException("Not a regular file: " + rsrc);
 		}
 
+		/**
+		 * Get the underlying resource of this entry.
+		 * 
+		 * @return the underlying resource
+		 */
+		public IResource getResource() {
+			return rsrc;
+		}
+
 		private File asFile() {
 			return ((IFile) rsrc).getLocation().toFile();
 		}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
index 189fc86..ce247f2 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
@@ -739,6 +739,15 @@ public int isPathPrefix(final byte[] p, final int pLen) {
 	}
 
 	/**
+	 * Get the current subtree depth of this walker.
+	 * 
+	 * @return the current subtree depth of this walker.
+	 */
+	public int getDepth() {
+		return depth;
+	}
+
+	/**
 	 * Is the current entry a subtree?
 	 * <p>
 	 * This method is faster then testing the raw mode bits of all trees to see
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH v2 12/12] Implement label decorations for folders and projects
  2009-02-11 18:40                     ` [EGIT PATCH v2 11/12] Expose the underlying resource entries in ContainerTreeIterator Tor Arne Vestbø
@ 2009-02-11 18:40                       ` Tor Arne Vestbø
  2009-02-12  0:02                         ` Robin Rosenberg
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 18:40 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

The option "Re-decorate ancestors..." controls if parents of a
re-decorated resource also should be updated, for example to
to signal that the containing folder is now concidered dirty.

The option "Maximum number of levels..." controls how deep the
container decoration algorithm will recurse when trying to
determine the state (dirty, staged, etc.) of a container.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../egit/ui/PluginPreferenceInitializer.java       |    4 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    4 +-
 .../src/org/spearce/egit/ui/UIText.java            |   14 +-
 .../decorators/DecoratableResourceAdapter.java     |  391 ++++++++++++++++++++
 .../decorators/GitLightweightDecorator.java        |  300 ++-------------
 .../preferences/GitDecoratorPreferencePage.java    |   91 ++++-
 .../src/org/spearce/egit/ui/uitext.properties      |    7 +-
 7 files changed, 528 insertions(+), 283 deletions(-)
 create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
index a3196f4..8d617e9 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
@@ -35,7 +35,9 @@ public void initializeDefaultPreferences() {
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, true);
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_TOOLTIPS, false);
 
-		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
+		prefs.setDefault(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS, true);
+		prefs.setDefault(UIPreferences.DECORATOR_RECURSIVE_LIMIT,
+				Integer.MAX_VALUE);
 		prefs.setDefault(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_fileFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
index e812716..bbc36be 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
@@ -53,7 +53,9 @@
 	public final static String THEME_CommitMessageFont = "org.spearce.egit.ui.CommitMessageFont";
 
 	/** */
-	public final static String DECORATOR_CALCULATE_DIRTY = "decorator_calculate_dirty";
+	public final static String DECORATOR_RECOMPUTE_ANCESTORS = "decorator_recompute_ancestors";
+	/** */
+	public final static String DECORATOR_RECURSIVE_LIMIT = "decorator_recursive_limit";
 	/** */
 	public final static String DECORATOR_FILETEXT_DECORATION = "decorator_filetext_decoration";
 	/** */
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 7e26337..bd64ca1 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -935,7 +935,19 @@
 	public static String DecoratorPreferencesPage_addVariablesAction;
 
 	/** */
-	public static String DecoratorPreferencesPage_computeDeep;
+	public static String DecoratorPreferencesPage_recomputeAncestorDecorations;
+
+	/** */
+	public static String DecoratorPreferencesPage_recomputeAncestorDecorationsTooltip;
+
+	/** */
+	public static String DecoratorPreferencesPage_computeRecursiveLimit;
+
+	/** */
+	public static String DecoratorPreferencesPage_computeRecursiveLimitTooltip;
+
+	/** */
+	public static String DecoratorPreferencesPage_invalidInput;
 
 	/** */
 	public static String DecoratorPreferencesPage_description;
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
new file mode 100644
index 0000000..e2fe54b
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
@@ -0,0 +1,391 @@
+/*******************************************************************************
+ * Copyright (C) 2007, IBM Corporation and others
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.ui.internal.decorators;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.team.core.Team;
+import org.spearce.egit.core.ContainerTreeIterator;
+import org.spearce.egit.core.ContainerTreeIterator.ResourceEntry;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIPreferences;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheEntry;
+import org.spearce.jgit.dircache.DirCacheIterator;
+import org.spearce.jgit.errors.IncorrectObjectTypeException;
+import org.spearce.jgit.errors.MissingObjectException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.treewalk.EmptyTreeIterator;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.AndTreeFilter;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
+import org.spearce.jgit.treewalk.filter.TreeFilter;
+
+class DecoratableResourceAdapter implements IDecoratableResource {
+
+	private final IResource resource;
+
+	private final RepositoryMapping mapping;
+
+	private final Repository repository;
+
+	private final ObjectId headId;
+
+	private final IPreferenceStore store;
+
+	private String branch = "";
+
+	private boolean tracked = false;
+
+	private boolean ignored = false;
+
+	private boolean dirty = false;
+
+	private boolean conflicts = false;
+
+	private boolean assumeValid = false;
+
+	private Staged staged = Staged.NOT_STAGED;
+
+	static final int T_HEAD = 0;
+
+	static final int T_INDEX = 1;
+
+	static final int T_WORKSPACE = 2;
+
+	@SuppressWarnings("fallthrough")
+	public DecoratableResourceAdapter(IResource resourceToWrap)
+			throws IOException {
+		resource = resourceToWrap;
+		mapping = RepositoryMapping.getMapping(resource);
+		repository = mapping.getRepository();
+		headId = repository.resolve(Constants.HEAD);
+
+		store = Activator.getDefault().getPreferenceStore();
+
+		// TODO: Add option to shorten branch name to 6 chars if it's a SHA
+		branch = repository.getBranch();
+
+		TreeWalk treeWalk = createThreeWayTreeWalk();
+		if (treeWalk == null)
+			return;
+
+		switch (resource.getType()) {
+		case IResource.FILE:
+			if (!treeWalk.next())
+				return;
+			extractResourceProperties(treeWalk);
+			break;
+		case IResource.PROJECT:
+			tracked = true;
+		case IResource.FOLDER:
+			extractContainerProperties(treeWalk);
+			break;
+		}
+	}
+
+	private void extractResourceProperties(TreeWalk treeWalk) {
+		final ContainerTreeIterator workspaceIterator = treeWalk.getTree(
+				T_WORKSPACE, ContainerTreeIterator.class);
+		final ResourceEntry resourceEntry = workspaceIterator != null ? workspaceIterator
+				.getResourceEntry() : null;
+
+		if (resourceEntry == null)
+			return;
+
+		if (isIgnored(resourceEntry.getResource())) {
+			ignored = true;
+			return;
+		}
+
+		final int mHead = treeWalk.getRawMode(T_HEAD);
+		final int mIndex = treeWalk.getRawMode(T_INDEX);
+
+		if (mHead == FileMode.MISSING.getBits()
+				&& mIndex == FileMode.MISSING.getBits())
+			return;
+
+		tracked = true;
+
+		if (mHead == FileMode.MISSING.getBits()) {
+			staged = Staged.ADDED;
+		} else if (mIndex == FileMode.MISSING.getBits()) {
+			staged = Staged.REMOVED;
+		} else if (mHead != mIndex
+				|| (mIndex != FileMode.TREE.getBits() && !treeWalk.idEqual(
+						T_HEAD, T_INDEX))) {
+			staged = Staged.MODIFIED;
+		} else {
+			staged = Staged.NOT_STAGED;
+		}
+
+		final DirCacheIterator indexIterator = treeWalk.getTree(T_INDEX,
+				DirCacheIterator.class);
+		final DirCacheEntry indexEntry = indexIterator != null ? indexIterator
+				.getDirCacheEntry() : null;
+
+		if (indexEntry == null)
+			return;
+
+		if (indexEntry.getStage() > 0)
+			conflicts = true;
+
+		if (indexEntry.isAssumeValid()) {
+			dirty = false;
+			assumeValid = true;
+		} else {
+			if (!timestampMatches(indexEntry, resourceEntry))
+				dirty = true;
+
+			// TODO: Consider doing a content check here, to rule out false
+			// positives, as we might get mismatch between timestamps, even
+			// if the content is the same.
+		}
+	}
+
+	private class RecursiveStateFilter extends TreeFilter {
+
+		private int filesChecked = 0;
+
+		private int targetDepth = -1;
+
+		private final int recurseLimit;
+
+		public RecursiveStateFilter() {
+			recurseLimit = store
+					.getInt(UIPreferences.DECORATOR_RECURSIVE_LIMIT);
+		}
+
+		@Override
+		public boolean include(TreeWalk treeWalk)
+				throws MissingObjectException, IncorrectObjectTypeException,
+				IOException {
+
+			if (treeWalk.getFileMode(T_HEAD) == FileMode.MISSING
+					&& treeWalk.getFileMode(T_INDEX) == FileMode.MISSING)
+				return false;
+
+			if (FileMode.TREE.equals(treeWalk.getRawMode(T_WORKSPACE)))
+				return shouldRecurse(treeWalk);
+
+			// Backup current state so far
+			Staged wasStaged = staged;
+			boolean wasDirty = dirty;
+			boolean hadConflicts = conflicts;
+
+			extractResourceProperties(treeWalk);
+			filesChecked++;
+
+			// Merge results with old state
+			ignored = false;
+			assumeValid = false;
+			dirty = wasDirty || dirty;
+			conflicts = hadConflicts || conflicts;
+			if (staged != wasStaged && filesChecked > 1)
+				staged = Staged.MODIFIED;
+
+			return false;
+		}
+
+		private boolean shouldRecurse(TreeWalk treeWalk) {
+			final ContainerTreeIterator workspaceIterator = treeWalk.getTree(
+					T_WORKSPACE, ContainerTreeIterator.class);
+			final ResourceEntry resourceEntry = workspaceIterator != null ? workspaceIterator
+					.getResourceEntry()
+					: null;
+			IResource visitingResource = resourceEntry.getResource();
+
+			if (targetDepth == -1) {
+				if (visitingResource.equals(resource)
+						|| visitingResource.getParent().equals(resource))
+					targetDepth = treeWalk.getDepth();
+				else
+					return true;
+			}
+
+			if ((treeWalk.getDepth() - targetDepth) >= recurseLimit) {
+				if (visitingResource.equals(resource))
+					extractResourceProperties(treeWalk);
+
+				return false;
+			}
+
+			return true;
+		}
+
+		@Override
+		public TreeFilter clone() {
+			RecursiveStateFilter clone = new RecursiveStateFilter();
+			clone.filesChecked = this.filesChecked;
+			return clone;
+		}
+
+		@Override
+		public boolean shouldBeRecursive() {
+			return true;
+		}
+	}
+
+	private void extractContainerProperties(TreeWalk treeWalk) throws IOException {
+
+		if (isIgnored(resource)) {
+			ignored = true;
+			return;
+		}
+
+		treeWalk.setFilter(AndTreeFilter.create(treeWalk.getFilter(),
+				new RecursiveStateFilter()));
+		treeWalk.setRecursive(true);
+
+		treeWalk.next();
+	}
+
+	/**
+	 * Adds a filter to the specified tree walk limiting the results to only
+	 * those matching the resource specified by <code>resourceToFilterBy</code>
+	 * <p>
+	 * If the resource does not exists in the current repository, no filter is
+	 * added and the method returns <code>false</code>. If the resource is a
+	 * project, no filter is added, but the operation is considered a success.
+	 *
+	 * @param treeWalk
+	 *            the tree walk to add the filter to
+	 * @param resourceToFilterBy
+	 *            the resource to filter by
+	 *
+	 * @return <code>true</code> if the filter could be added,
+	 *         <code>false</code> otherwise
+	 */
+	private boolean addResourceFilter(final TreeWalk treeWalk,
+			final IResource resourceToFilterBy) {
+		Set<String> repositoryPaths = Collections.singleton(mapping
+				.getRepoRelativePath(resourceToFilterBy));
+		if (repositoryPaths.isEmpty())
+			return false;
+
+		if (repositoryPaths.contains(""))
+			return true; // Project filter
+
+		treeWalk.setFilter(PathFilterGroup.createFromStrings(repositoryPaths));
+		return true;
+	}
+
+	/**
+	 * Helper method to create a new tree walk between the repository, the
+	 * index, and the working tree.
+	 *
+	 * @return the created tree walk, or null if it could not be created
+	 * @throws IOException
+	 *             if there were errors when creating the tree walk
+	 */
+	private TreeWalk createThreeWayTreeWalk() throws IOException {
+		final TreeWalk treeWalk = new TreeWalk(repository);
+		if (!addResourceFilter(treeWalk, resource))
+			return null;
+
+		treeWalk.setRecursive(treeWalk.getFilter().shouldBeRecursive());
+		treeWalk.reset();
+
+		// Repository
+		if (headId != null)
+			treeWalk.addTree(new RevWalk(repository).parseTree(headId));
+		else
+			treeWalk.addTree(new EmptyTreeIterator());
+
+		// Index
+		treeWalk.addTree(new DirCacheIterator(DirCache.read(repository)));
+
+		// Working directory
+		IProject project = resource.getProject();
+		IWorkspace workspace = resource.getWorkspace();
+		if (repository.getWorkDir().equals(project.getLocation().toFile()))
+			treeWalk.addTree(new ContainerTreeIterator(project));
+		else
+			treeWalk.addTree(new ContainerTreeIterator(workspace.getRoot()));
+
+		// TODO: Add fallback for projects with the repository more than
+		// one parent up, for example by using a stack of DummyIterators
+
+		return treeWalk;
+	}
+
+	private static boolean timestampMatches(DirCacheEntry indexEntry,
+			ResourceEntry resourceEntry) {
+		long tIndex = indexEntry.getLastModified();
+		long tWorkspaceResource = resourceEntry.getLastModified();
+
+
+		// C-Git under Windows stores timestamps with 1-seconds resolution,
+		// so we need to check to see if this is the case here, and possibly
+		// fix the timestamp of the resource to match the resolution of the
+		// index.
+		if (tIndex % 1000 == 0) {
+			return tIndex == (tWorkspaceResource - (tWorkspaceResource % 1000));
+		} else {
+			return tIndex == tWorkspaceResource;
+		}
+	}
+
+	private static boolean isIgnored(IResource resource) {
+		// TODO: Also read ignores from .git/info/excludes et al.
+		return Team.isIgnoredHint(resource);
+	}
+
+	public String getName() {
+		return resource.getName();
+	}
+
+	public int getType() {
+		return resource.getType();
+	}
+
+	public String getBranch() {
+		return branch;
+	}
+
+	public boolean isTracked() {
+		return tracked;
+	}
+
+	public boolean isIgnored() {
+		return ignored;
+	}
+
+	public boolean isDirty() {
+		return dirty;
+	}
+
+	public Staged staged() {
+		return staged;
+	}
+
+	public boolean hasConflicts() {
+		return conflicts;
+	}
+
+	public boolean isAssumeValid() {
+		return assumeValid;
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index 1e95369..d9de3a4 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -14,7 +14,6 @@
 package org.spearce.egit.ui.internal.decorators;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -42,7 +41,6 @@
 import org.eclipse.osgi.util.TextProcessor;
 import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.widgets.Display;
-import org.eclipse.team.core.Team;
 import org.eclipse.team.ui.ISharedImages;
 import org.eclipse.team.ui.TeamImages;
 import org.eclipse.team.ui.TeamUI;
@@ -58,21 +56,11 @@
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
 import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
-import org.spearce.jgit.dircache.DirCache;
-import org.spearce.jgit.dircache.DirCacheEntry;
-import org.spearce.jgit.dircache.DirCacheIterator;
-import org.spearce.jgit.lib.Constants;
-import org.spearce.jgit.lib.FileMode;
 import org.spearce.jgit.lib.IndexChangedEvent;
-import org.spearce.jgit.lib.ObjectId;
 import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.lib.RepositoryChangedEvent;
 import org.spearce.jgit.lib.RepositoryListener;
-import org.spearce.jgit.revwalk.RevWalk;
-import org.spearce.jgit.treewalk.EmptyTreeIterator;
-import org.spearce.jgit.treewalk.TreeWalk;
-import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 
 /**
  * Supplies annotations for displayed resources
@@ -151,6 +139,15 @@ public void decorate(Object element, IDecoration decoration) {
 		if (resource == null)
 			return;
 
+		// Don't decorate if the workbench is not running
+		if (!PlatformUI.isWorkbenchRunning())
+			return;
+
+		// Don't decorate if UI plugin is not running
+		Activator activator = Activator.getDefault();
+		if (activator == null)
+			return;
+
 		// Don't decorate the workspace root
 		if (resource.getType() == IResource.ROOT)
 			return;
@@ -169,11 +166,6 @@ public void decorate(Object element, IDecoration decoration) {
 		if (mapping.getRepoRelativePath(resource) == null)
 			return;
 
-		// Don't decorate if UI plugin is not running
-		Activator activator = Activator.getDefault();
-		if (activator == null)
-			return;
-
 		try {
 			DecorationHelper helper = new DecorationHelper(activator
 					.getPreferenceStore());
@@ -184,238 +176,6 @@ public void decorate(Object element, IDecoration decoration) {
 		}
 	}
 
-	private class DecoratableResourceAdapter implements IDecoratableResource {
-
-		private final IResource resource;
-
-		private final RepositoryMapping mapping;
-
-		private final Repository repository;
-
-		private final ObjectId headId;
-
-		private String branch = "";
-
-		private boolean tracked = false;
-
-		private boolean ignored = false;
-
-		private boolean dirty = false;
-
-		private boolean conflicts = false;
-
-		private boolean assumeValid = false;
-
-		private Staged staged = Staged.NOT_STAGED;
-
-		static final int T_HEAD = 0;
-
-		static final int T_INDEX = 1;
-
-		static final int T_WORKSPACE = 2;
-
-		public DecoratableResourceAdapter(IResource resourceToWrap)
-				throws IOException {
-			resource = resourceToWrap;
-			mapping = RepositoryMapping.getMapping(resource);
-			repository = mapping.getRepository();
-			headId = repository.resolve(Constants.HEAD);
-
-			switch (resource.getType()) {
-			case IResource.FILE:
-				extractFileProperties();
-				break;
-			case IResource.FOLDER:
-				extractContainerProperties();
-				break;
-			case IResource.PROJECT:
-				extractProjectProperties();
-				break;
-			}
-		}
-
-		private void extractFileProperties() throws IOException {
-			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
-			if (treeWalk == null)
-				return;
-
-			if (treeWalk.next())
-				tracked = true;
-			else
-				return;
-
-			// TODO: Also read ignores from .git/info/excludes et al.
-			if (Team.isIgnoredHint(resource)) {
-				ignored = true;
-				return;
-			}
-
-			final DirCacheIterator indexIterator = treeWalk.getTree(T_INDEX,
-					DirCacheIterator.class);
-			final DirCacheEntry indexEntry = indexIterator != null ? indexIterator
-					.getDirCacheEntry()
-					: null;
-
-			if (indexEntry == null) {
-				staged = Staged.REMOVED;
-			} else {
-				if (indexEntry.isAssumeValid()) {
-					dirty = false;
-					assumeValid = true;
-				} else if (indexEntry.getStage() > 0) {
-					conflicts = true;
-				} else if (treeWalk.getRawMode(T_HEAD) == FileMode.MISSING
-						.getBits()) {
-					staged = Staged.ADDED;
-				} else {
-					long indexEntryLastModified = indexEntry.getLastModified();
-					long resourceLastModified = resource.getLocalTimeStamp();
-
-					// C-Git under Windows stores timestamps with 1-seconds
-					// resolution, so we need to check to see if this is the
-					// case here, and possibly fix the timestamp of the resource
-					// to match the resolution of the index.
-					if (indexEntryLastModified % 1000 == 0) {
-						resourceLastModified -= resourceLastModified % 1000;
-					}
-
-					if (resourceLastModified != indexEntryLastModified) {
-						// TODO: Consider doing a content check here, to rule
-						// out false positives, as we might get mismatch between
-						// timestamps, even if the content is the same
-						dirty = true;
-					}
-
-					if (treeWalk.getRawMode(T_HEAD) != treeWalk
-							.getRawMode(T_INDEX)
-							|| !treeWalk.idEqual(T_HEAD, T_INDEX)) {
-						staged = Staged.MODIFIED;
-					}
-				}
-			}
-
-		}
-
-		private void extractContainerProperties() throws IOException {
-			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
-			if (treeWalk == null)
-				return;
-
-			if (treeWalk.next())
-				tracked = true;
-			else
-				return;
-
-			// TODO: Also read ignores from .git/info/excludes et al.
-			if (Team.isIgnoredHint(resource)) {
-				ignored = true;
-				return;
-			}
-
-			// TODO: Compute dirty state for folder, using ContainerTreeIterator
-			// and ContainerDiffFilter
-
-		}
-
-		private void extractProjectProperties() throws IOException {
-			branch = repository.getBranch();
-			tracked = true;
-
-			// TODO: Compute dirty state for folder, using ContainerTreeIterator
-			// and ContainerDiffFilter
-
-		}
-
-		/**
-		 * Adds a filter to the specified tree walk limiting the results to only
-		 * those matching the resource specified by
-		 * <code>resourceToFilterBy</code>
-		 * <p>
-		 * If the resource does not exists in the current repository, or it has
-		 * an empty path (it is the project itself), the filter is not added,
-		 * and the method returns <code>null</code>.
-		 * 
-		 * @param treeWalk
-		 *            the tree walk to add the filter to
-		 * @param resourceToFilterBy
-		 *            the resource to filter by
-		 * 
-		 * @return <code>true</code> if the filter could be added,
-		 *         <code>false</code> otherwise
-		 */
-		private boolean addResourceFilter(final TreeWalk treeWalk,
-				final IResource resourceToFilterBy) {
-			Set<String> repositoryPaths = Collections.singleton(mapping
-					.getRepoRelativePath(resourceToFilterBy));
-			if (repositoryPaths.isEmpty() || repositoryPaths.contains(""))
-				return false;
-
-			treeWalk.setFilter(PathFilterGroup
-					.createFromStrings(repositoryPaths));
-			return true;
-		}
-
-		/**
-		 * Helper method to create a new tree walk between HEAD and the index.
-		 * 
-		 * @return the created tree walk, or null if it could not be created
-		 * @throws IOException
-		 *             if there were errors when creating the tree walk
-		 */
-		private TreeWalk createHeadVsIndexTreeWalk() throws IOException {
-			final TreeWalk treeWalk = new TreeWalk(repository);
-			if (!addResourceFilter(treeWalk, resource))
-				return null;
-
-			treeWalk.setRecursive(treeWalk.getFilter().shouldBeRecursive());
-			treeWalk.reset();
-
-			if (headId != null)
-				treeWalk.addTree(new RevWalk(repository).parseTree(headId));
-			else
-				treeWalk.addTree(new EmptyTreeIterator());
-
-			treeWalk.addTree(new DirCacheIterator(DirCache.read(repository)));
-			return treeWalk;
-		}
-
-		public String getName() {
-			return resource.getName();
-		}
-
-		public int getType() {
-			return resource.getType();
-		}
-
-		public String getBranch() {
-			return branch;
-		}
-
-		public boolean isTracked() {
-			return tracked;
-		}
-
-		public boolean isIgnored() {
-			return ignored;
-		}
-
-		public boolean isDirty() {
-			return dirty;
-		}
-
-		public Staged staged() {
-			return staged;
-		}
-
-		public boolean hasConflicts() {
-			return conflicts;
-		}
-
-		public boolean isAssumeValid() {
-			return assumeValid;
-		}
-	}
-
 	/**
 	 * Helper class for doing resource decoration, based on the given
 	 * preferences
@@ -570,11 +330,13 @@ else if (staged == Staged.REMOVED)
 				}
 
 				// Conflicts override everything
-				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON)
+				if (store
+						.getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON)
 						&& resource.hasConflicts())
 					overlay = conflictImage;
 
-			} else if (store.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
+			} else if (store
+					.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
 				overlay = untrackedImage;
 			}
 
@@ -714,6 +476,14 @@ public void resourceChanged(IResourceChangeEvent event) {
 		try { // Compute the changed resources by looking at the delta
 			event.getDelta().accept(new IResourceDeltaVisitor() {
 				public boolean visit(IResourceDelta delta) throws CoreException {
+
+					// If the file has changed but not in a way that we care
+					// about (e.g. marker changes to files) then ignore
+					if (delta.getKind() == IResourceDelta.CHANGED
+							&& (delta.getFlags() & INTERESTING_CHANGES) == 0) {
+						return true;
+					}
+
 					final IResource resource = delta.getResource();
 
 					// If the resource is not part of a project under Git
@@ -724,6 +494,7 @@ public boolean visit(IResourceDelta delta) throws CoreException {
 						// Ignore the change
 						return true;
 					}
+
 					if (resource.getType() == IResource.ROOT) {
 						// Continue with the delta
 						return true;
@@ -735,33 +506,32 @@ public boolean visit(IResourceDelta delta) throws CoreException {
 							return false;
 					}
 
-					// If the file has changed but not in a way that we care
-					// about
-					// (e.g. marker changes to files) then ignore the change
-					if (delta.getKind() == IResourceDelta.CHANGED
-							&& (delta.getFlags() & INTERESTING_CHANGES) == 0) {
-						return true;
-					}
-
 					// All seems good, schedule the resource for update
 					resourcesToUpdate.add(resource);
-					return true;
+
+					if (delta.getKind() == IResourceDelta.CHANGED
+							&& (delta.getFlags() & IResourceDelta.OPEN) > 1)
+						return false; // Don't recurse when opening projects
+					else
+						return true;
 				}
 			}, true /* includePhantoms */);
 		} catch (final CoreException e) {
 			handleException(null, e);
 		}
 
-		// If deep decorator calculation is enabled in the preferences we
-		// walk the ancestor tree of each of the changed resources and add
+		if (resourcesToUpdate.isEmpty())
+			return;
+
+		// If ancestor-decoration is enabled in the preferences we walk
+		// the ancestor tree of each of the changed resources and add
 		// their parents to the update set
 		final IPreferenceStore store = Activator.getDefault()
 				.getPreferenceStore();
-		if (store.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)) {
+		if (store.getBoolean(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS)) {
 			final IResource[] changedResources = resourcesToUpdate
 					.toArray(new IResource[resourcesToUpdate.size()]);
-			for (int i = 0; i < changedResources.length; i++) {
-				IResource current = changedResources[i];
+			for (IResource current : changedResources) {
 				while (current.getType() != IResource.ROOT) {
 					current = current.getParent();
 					resourcesToUpdate.add(current);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
index f72ceb7..1c0f82e 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -51,12 +51,14 @@
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Scale;
 import org.eclipse.swt.widgets.TabFolder;
 import org.eclipse.swt.widgets.TabItem;
 import org.eclipse.swt.widgets.Text;
@@ -76,6 +78,7 @@
 import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator.DecorationHelper;
 import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
 
+
 /**
  * Preference page for customizing Git label decorations
  */
@@ -88,7 +91,9 @@
 
 	private Text projectTextFormat;
 
-	private Button computeDeepDirtyState;
+	private Button recomputeAncestorDecorations;
+
+	private Scale containerRecurseLimit;
 
 	private Button showTracked;
 
@@ -108,11 +113,11 @@
 
 	static {
 		final PreviewResource project = new PreviewResource(
-				"Project", IResource.PROJECT, "master", true, false, false, Staged.NOT_STAGED, false, false); //$NON-NLS-1$1
+				"Project", IResource.PROJECT, "master", true, false, true, Staged.NOT_STAGED, false, false); //$NON-NLS-1$1
 		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
 
 		children.add(new PreviewResource(
-						"folder", IResource.FOLDER, null, true, false, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
+						"folder", IResource.FOLDER, null, true, false, true, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
 						"tracked.txt", IResource.FILE, null, true, false, false, Staged.NOT_STAGED, false, false)); //$NON-NLS-1$
 		children.add(new PreviewResource(
@@ -194,14 +199,68 @@ public void propertyChange(PropertyChangeEvent event) {
 
 	private Control createGeneralDecoratorPage(Composite parent) {
 		Composite composite = SWTUtils.createHVFillComposite(parent,
-				SWTUtils.MARGINS_DEFAULT);
+				SWTUtils.MARGINS_DEFAULT, 1);
 
-		computeDeepDirtyState = SWTUtils.createCheckBox(composite,
-				UIText.DecoratorPreferencesPage_computeDeep);
+		recomputeAncestorDecorations = SWTUtils.createCheckBox(composite,
+				UIText.DecoratorPreferencesPage_recomputeAncestorDecorations);
+		recomputeAncestorDecorations
+				.setToolTipText(UIText.DecoratorPreferencesPage_recomputeAncestorDecorationsTooltip);
+
+		SWTUtils.createLabel(composite,
+				UIText.DecoratorPreferencesPage_computeRecursiveLimit);
+		containerRecurseLimit = createLabeledScaleControl(composite);
+		containerRecurseLimit
+				.setToolTipText(UIText.DecoratorPreferencesPage_computeRecursiveLimitTooltip);
 
 		return composite;
 	}
 
+	private Scale createLabeledScaleControl(Composite parent) {
+
+		final int[] values = new int[] { 0, 1, 2, 3, 5, 10, 15, 20, 50, 100,
+				Integer.MAX_VALUE };
+
+		Composite composite = SWTUtils.createHVFillComposite(parent,
+				SWTUtils.MARGINS_DEFAULT);
+
+		Composite labels = SWTUtils.createHVFillComposite(composite,
+				SWTUtils.MARGINS_NONE, values.length);
+		GridLayout labelsLayout = (GridLayout) labels.getLayout();
+		labelsLayout.makeColumnsEqualWidth = true;
+		labelsLayout.horizontalSpacing = 0;
+		labels.setLayoutData(SWTUtils.createGridData(-1, -1, SWT.FILL,
+				SWT.FILL, false, false));
+
+		for (int i = 0; i < values.length; ++i) {
+			Label label = SWTUtils.createLabel(labels, "" + values[i]);
+			if (i == 0) {
+				label.setAlignment(SWT.LEFT);
+				label.setText("Off");
+			} else if (i == values.length - 1) {
+				label.setAlignment(SWT.RIGHT);
+				label.setText("Inf.");
+			} else {
+				label.setAlignment(SWT.CENTER);
+			}
+		}
+
+		final Scale scale = new Scale(composite, SWT.HORIZONTAL);
+		scale.setLayoutData(SWTUtils.createHVFillGridData());
+		scale.setMaximum(values.length - 1);
+		scale.setMinimum(0);
+		scale.setIncrement(1);
+		scale.setPageIncrement(1);
+
+		scale.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				// Workaround for GTK treating the slider as stepless
+				scale.setSelection(scale.getSelection());
+			}
+		});
+
+		return scale;
+	}
+
 	/**
 	 * Creates the controls for the first tab folder
 	 * 
@@ -301,8 +360,10 @@ public void handleEvent(Event event) {
 	private void initializeValues() {
 		final IPreferenceStore store = getPreferenceStore();
 
-		computeDeepDirtyState.setSelection(store
-				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		recomputeAncestorDecorations.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS));
+		containerRecurseLimit.setSelection(store
+				.getInt(UIPreferences.DECORATOR_RECURSIVE_LIMIT));
 
 		fileTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
@@ -328,7 +389,6 @@ public void widgetSelected(SelectionEvent e) {
 			}
 		};
 
-		computeDeepDirtyState.addSelectionListener(selectionListener);
 		showTracked.addSelectionListener(selectionListener);
 		showUntracked.addSelectionListener(selectionListener);
 		showStaged.addSelectionListener(selectionListener);
@@ -371,8 +431,10 @@ public boolean performOk() {
 	 */
 	private boolean performOk(IPreferenceStore store) {
 
-		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY,
-				computeDeepDirtyState.getSelection());
+		store.setValue(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS,
+				recomputeAncestorDecorations.getSelection());
+		store.setValue(UIPreferences.DECORATOR_RECURSIVE_LIMIT,
+				containerRecurseLimit.getSelection());
 
 		store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				fileTextFormat.getText());
@@ -403,8 +465,11 @@ protected void performDefaults() {
 		super.performDefaults();
 		IPreferenceStore store = getPreferenceStore();
 
-		computeDeepDirtyState.setSelection(store
-				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		recomputeAncestorDecorations
+				.setSelection(store
+						.getDefaultBoolean(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS));
+		containerRecurseLimit.setSelection(store
+				.getDefaultInt(UIPreferences.DECORATOR_RECURSIVE_LIMIT));
 
 		fileTextFormat.setText(store
 				.getDefaultString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index e9a2321..847cb2c 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -350,9 +350,12 @@ Decorator_exceptionMessage=Errors occurred while applying Git decorations to res
 
 DecoratorPreferencesPage_addVariablesTitle=Add Variables
 DecoratorPreferencesPage_addVariablesAction=Add &Variables...
-DecoratorPreferencesPage_computeDeep=Include &ancestors when re-decorating changed resources
+DecoratorPreferencesPage_recomputeAncestorDecorations=Re-decorate &ancestors when decorating changed resources
+DecoratorPreferencesPage_recomputeAncestorDecorationsTooltip=Enabling this option will cause the ancestor-tree of any updated resources to also be re-decorated (minor performance impact).
+DecoratorPreferencesPage_computeRecursiveLimit=Maximum number of levels to recurse for container decoration:
+DecoratorPreferencesPage_computeRecursiveLimitTooltip=This value determines the maximum number of levels that will be recursed for each container decoration before bailing out. Increasing this number will improve accuracy for container decoration, but has a performance impact for large projects.
 DecoratorPreferencesPage_description=Shows Git specific information on resources in projects under version control.
-
+DecoratorPreferencesPage_invalidInput=''{0}'' is not a valid input
 DecoratorPreferencesPage_decorationSettings=Decoration &settings:
 DecoratorPreferencesPage_preview=Preview:
 DecoratorPreferencesPage_fileFormatLabel=&Files:
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources
  2009-02-11 18:40               ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
  2009-02-11 18:40                 ` [EGIT PATCH v2 09/12] Implement icon and text decorations of various resource states Tor Arne Vestbø
@ 2009-02-11 22:16                 ` Robin Rosenberg
  2009-02-11 22:46                   ` [EGIT PATCH 08/12 v3] " Tor Arne Vestbø
  1 sibling, 1 reply; 21+ messages in thread
From: Robin Rosenberg @ 2009-02-11 22:16 UTC (permalink / raw)
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git


This one fails to compile, 

Description	Resource	Path	Location	Type
fPreview cannot be resolved	GitDecoratorPreferencePage.java	org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences	line 279	Java Problem

With patch 9 things build again, but swapping the patches does not help.

Every patch should build and pass unit tests so we can perform bisect over the patches.

-- robin

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [EGIT PATCH 08/12 v3] Add icon decoration for tracked and untracked resources
  2009-02-11 22:16                 ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Robin Rosenberg
@ 2009-02-11 22:46                   ` Tor Arne Vestbø
  0 siblings, 0 replies; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-11 22:46 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Can be enabled/disabled in the preferences

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---

Sorry, got a little trigger happy with the rebasing and messed up a merge :/ 

org.spearce.egit.ui/icons/ovr/shared.gif           |  Bin 106 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/untracked.gif        |  Bin 0 -> 79 bytes
 .../egit/ui/PluginPreferenceInitializer.java       |    4 +-
 .../src/org/spearce/egit/ui/UIIcons.java           |    6 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    4 +
 .../src/org/spearce/egit/ui/UIText.java            |    6 +
 .../decorators/GitLightweightDecorator.java        |  162 ++++++++++++++++++--
 .../internal/decorators/IDecoratableResource.java  |   15 ++
 .../preferences/GitDecoratorPreferencePage.java    |   89 +++++++++--
 .../src/org/spearce/egit/ui/uitext.properties      |    2 +
 10 files changed, 259 insertions(+), 29 deletions(-)
 delete mode 100644 org.spearce.egit.ui/icons/ovr/shared.gif
 create mode 100644 org.spearce.egit.ui/icons/ovr/untracked.gif

diff --git a/org.spearce.egit.ui/icons/ovr/shared.gif b/org.spearce.egit.ui/icons/ovr/shared.gif
deleted file mode 100644
index eb71a3c742e133c2ed61c958e237c71e0f7cb6aa..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?wbhEHbWM|-D*v!E2|KHF5k5c|0Q~7_){{ITmE8U{M-dx?8EBgQce+Fzo@h1x-
p15m3DND^cQ1B<x8$xa82iYrMhmeJo^<v0pdEtw8K%o1d<1^~aoAP)cl

diff --git a/org.spearce.egit.ui/icons/ovr/untracked.gif b/org.spearce.egit.ui/icons/ovr/untracked.gif
new file mode 100644
index 0000000000000000000000000000000000000000..45ca32060700d71abcb88bbcdb1000d503fe11cf
GIT binary patch
literal 79
zcmZ?wbhEHbWM|-DSj51<z}U+mGMhnsAy57};pVIV!2l?%_>+Z^fq{)d2gqgssbydm
biP#m!(lAAgdxkboj%vJ&f(EaJDuXouU6m8v

literal 0
HcmV?d00001

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
index 79c2665..7465444 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java
@@ -35,13 +35,15 @@ public void initializeDefaultPreferences() {
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, true);
 		prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_TOOLTIPS, false);
 
+		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
 		prefs.setDefault(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_fileFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_folderFormatDefault);
 		prefs.setDefault(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
 				UIText.DecoratorPreferencesPage_projectFormatDefault);
-		prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_TRACKED_ICON, true);
+		prefs.setDefault(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON, true);
 
 		w = new int[] { 500, 500 };
 		UIPreferences.setDefault(prefs,
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
index ced186e..4c0d189 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIIcons.java
@@ -24,8 +24,8 @@
 	/** Decoration for resource removed from the index but not commit. */
 	public static final ImageDescriptor OVR_PENDING_REMOVE;
 
-	/** Decoration for resource tracked and committed in git. */
-	public static final ImageDescriptor OVR_SHARED;
+	/** Decoration for resource not being tracked by Git */
+	public static final ImageDescriptor OVR_UNTRACKED;
 
 	/** Decoration for tracked resource with a merge conflict.  */
 	public static final ImageDescriptor OVR_CONFLICT;
@@ -86,7 +86,7 @@
 		base = init();
 		OVR_PENDING_ADD = map("ovr/pending_add.gif");
 		OVR_PENDING_REMOVE = map("ovr/pending_remove.gif");
-		OVR_SHARED = map("ovr/shared.gif");
+		OVR_UNTRACKED = map("ovr/untracked.gif");
 		OVR_CONFLICT = map("ovr/conflict.gif");
 		OVR_ASSUMEVALID = map("ovr/assumevalid.gif");
 		ELCL16_FIND = map("elcl16/find.gif");
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
index a6168a0..7916cea 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java
@@ -60,6 +60,10 @@
 	public final static String DECORATOR_FOLDERTEXT_DECORATION = "decorator_foldertext_decoration";
 	/** */
 	public final static String DECORATOR_PROJECTTEXT_DECORATION = "decorator_projecttext_decoration";
+	/** */
+	public final static String DECORATOR_SHOW_TRACKED_ICON = "decorator_show_tracked_icon";
+	/** */
+	public final static String DECORATOR_SHOW_UNTRACKED_ICON = "decorator_show_untracked_icon";
 
 	/**
 	 * Get the preference values associated with a fixed integer array.
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
index 345c66b..60e4eaa 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java
@@ -988,6 +988,12 @@
 	/** */
 	public static String DecoratorPreferencesPage_labelDecorationsLink;
 
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowTracked;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowUntracked;
+
 	static {
 		initializeMessages(UIText.class.getPackage().getName() + ".uitext",
 				UIText.class);
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
index 265d5a3..b20070a 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java
@@ -15,6 +15,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -34,6 +35,7 @@
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.IDecoration;
@@ -41,7 +43,11 @@
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
 import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.team.core.Team;
+import org.eclipse.team.ui.ISharedImages;
+import org.eclipse.team.ui.TeamImages;
 import org.eclipse.team.ui.TeamUI;
 import org.eclipse.ui.IContributorResourceAdapter;
 import org.eclipse.ui.PlatformUI;
@@ -51,13 +57,22 @@
 import org.spearce.egit.core.project.RepositoryChangeListener;
 import org.spearce.egit.core.project.RepositoryMapping;
 import org.spearce.egit.ui.Activator;
+import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheIterator;
+import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.ObjectId;
 import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.lib.RepositoryChangedEvent;
 import org.spearce.jgit.lib.RepositoryListener;
+import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.treewalk.EmptyTreeIterator;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 
 /**
  * Supplies annotations for displayed resources
@@ -144,7 +159,7 @@ public void decorate(Object element, IDecoration decoration) {
 		if (!resource.exists() && !resource.isPhantom())
 			return;
 
-		// Make sure we're dealing with a Git project
+		// Make sure we're dealing with a project under Git revision control
 		final RepositoryMapping mapping = RepositoryMapping
 				.getMapping(resource);
 		if (mapping == null)
@@ -171,14 +186,80 @@ public void decorate(Object element, IDecoration decoration) {
 
 	private class DecoratableResourceAdapter implements IDecoratableResource {
 
-		private IResource resource;
-		private String branch;
+		private final IResource resource;
 
-		public DecoratableResourceAdapter(IResource resourceToWrap) throws IOException {
+		private final RepositoryMapping mapping;
+
+		private final Repository repository;
+
+		private final ObjectId headId;
+
+		private String branch = "";
+
+		private boolean tracked = false;
+
+		private boolean ignored = false;
+
+		public DecoratableResourceAdapter(IResource resourceToWrap)
+				throws IOException {
 			resource = resourceToWrap;
-			RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
-			Repository repository = mapping.getRepository();
+			mapping = RepositoryMapping.getMapping(resource);
+			repository = mapping.getRepository();
+			headId = repository.resolve(Constants.HEAD);
+
+			initializeValues();
+		}
+
+		/**
+		 * Initialize the various values that are used for making decoration
+		 * decisions later on.
+		 * 
+		 * We might as well pre-load these now, instead of using lazy
+		 * initialization, because they are all read by the decorator when
+		 * building variable bindings and computing the preferred overlay.
+		 * 
+		 * @throws IOException
+		 */
+		private void initializeValues() throws IOException {
+
+			// Resolve current branch
 			branch = repository.getBranch();
+
+			// Resolve tracked state
+			if (getType() == IResource.PROJECT) {
+				tracked = true;
+			} else {
+				final TreeWalk treeWalk = new TreeWalk(repository);
+
+				Set<String> repositoryPaths = Collections.singleton(mapping
+						.getRepoRelativePath(resource));
+				if (!(repositoryPaths.isEmpty() || repositoryPaths.contains(""))) {
+					treeWalk.setFilter(PathFilterGroup
+							.createFromStrings(repositoryPaths));
+					treeWalk.setRecursive(treeWalk.getFilter()
+							.shouldBeRecursive());
+					treeWalk.reset();
+
+					if (headId != null)
+						treeWalk.addTree(new RevWalk(repository)
+								.parseTree(headId));
+					else
+						treeWalk.addTree(new EmptyTreeIterator());
+
+					treeWalk.addTree(new DirCacheIterator(DirCache
+							.read(repository)));
+					if (treeWalk.next()) {
+						tracked = true;
+					}
+				}
+			}
+
+			// Resolve ignored state (currently only reads the global Eclipse
+			// ignores)
+			// TODO: Also read ignores from .git/info/excludes et al.
+			if (Team.isIgnoredHint(resource)) {
+				ignored = true;
+			}
 		}
 
 		public String getName() {
@@ -192,6 +273,14 @@ public int getType() {
 		public String getBranch() {
 			return branch;
 		}
+
+		public boolean isTracked() {
+			return tracked;
+		}
+
+		public boolean isIgnored() {
+			return ignored;
+		}
 	}
 
 	/**
@@ -203,13 +292,45 @@ public String getBranch() {
 	 */
 	public static class DecorationHelper {
 
-		private IPreferenceStore store;
-
 		/** */
 		public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
+
 		/** */
 		public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
 
+		private IPreferenceStore store;
+
+		/**
+		 * Define a cached image descriptor which only creates the image data
+		 * once
+		 */
+		private static class CachedImageDescriptor extends ImageDescriptor {
+			ImageDescriptor descriptor;
+
+			ImageData data;
+
+			public CachedImageDescriptor(ImageDescriptor descriptor) {
+				this.descriptor = descriptor;
+			}
+
+			public ImageData getImageData() {
+				if (data == null) {
+					data = descriptor.getImageData();
+				}
+				return data;
+			}
+		}
+
+		private static ImageDescriptor trackedImage;
+
+		private static ImageDescriptor untrackedImage;
+
+		static {
+			trackedImage = new CachedImageDescriptor(TeamImages
+					.getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR));
+			untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED);
+		}
+
 		/**
 		 * Constructs a decorator using the rules from the given
 		 * <code>preferencesStore</code>
@@ -233,6 +354,12 @@ public DecorationHelper(IPreferenceStore preferencesStore) {
 		 */
 		public void decorate(IDecoration decoration,
 				IDecoratableResource resource) {
+			decorateText(decoration, resource);
+			decorateIcons(decoration, resource);
+		}
+
+		private void decorateText(IDecoration decoration,
+				IDecoratableResource resource) {
 			String format = "";
 			switch (resource.getType()) {
 			case IResource.FILE:
@@ -256,9 +383,24 @@ public void decorate(IDecoration decoration,
 			decorate(decoration, format, bindings);
 		}
 
+		private void decorateIcons(IDecoration decoration,
+				IDecoratableResource resource) {
+			if (resource.isIgnored())
+				return;
+
+			if (resource.isTracked()) {
+				if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON))
+					decoration.addOverlay(trackedImage);
+			} else if (store
+					.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
+				decoration.addOverlay(untrackedImage);
+			}
+		}
+
 		/**
-		 * Decorates the given <code>decoration</code>, using the given
-		 * <code>format</code>, and mapped using <code>bindings</code>
+		 * Decorates the given <code>decoration</code>, using the specified text
+		 * <code>format</code>, and mapped using the variable bindings from
+		 * <code>bindings</code>
 		 * 
 		 * @param decoration
 		 *            the decoration to decorate
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
index 6b36e0e..f144214 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java
@@ -36,4 +36,19 @@
 	 *         applicable
 	 */
 	String getBranch();
+
+	/**
+	 * Returns whether or not the resource is tracked by Git
+	 * 
+	 * @return whether or not the resource is tracked by Git
+	 */
+	boolean isTracked();
+
+	/**
+	 * Returns whether or not the resource is ignored, either by a global team
+	 * ignore in Eclipse, or by .git/info/exclude et al.
+	 * 
+	 * @return whether or not the resource is ignored
+	 */
+	boolean isIgnored();
 }
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
index b7d737c..06ddf50 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -44,6 +44,9 @@
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
@@ -83,7 +86,11 @@
 
 	private Text projectTextFormat;
 
-	private Button showDirty;
+	private Button computeDeepDirtyState;
+
+	private Button showTracked;
+
+	private Button showUntracked;
 
 	private Preview preview;
 
@@ -93,10 +100,16 @@
 
 	static {
 		final PreviewResource project = new PreviewResource(
-				"Project", IResource.PROJECT, "master"); //$NON-NLS-1$1
+				"Project", IResource.PROJECT, "master", true, false); //$NON-NLS-1$1
 		final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>();
-		children.add(new PreviewResource("folder", IResource.FOLDER, null)); //$NON-NLS-1$
-		children.add(new PreviewResource("file.txt", IResource.FILE, null)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"folder", IResource.FOLDER, null, true, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"file.txt", IResource.FILE, null, true, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"untracked.txt", IResource.FILE, null, false, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+				"ignored.txt", IResource.FILE, null, false, true)); //$NON-NLS-1$
 		project.children = children;
 		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
 	}
@@ -160,7 +173,7 @@ private Control createGeneralDecoratorPage(Composite parent) {
 		Composite composite = SWTUtils.createHVFillComposite(parent,
 				SWTUtils.MARGINS_DEFAULT);
 
-		showDirty = SWTUtils.createCheckBox(composite,
+		computeDeepDirtyState = SWTUtils.createCheckBox(composite,
 				UIText.DecoratorPreferencesPage_computeDeep);
 
 		return composite;
@@ -202,6 +215,11 @@ private Control createIconDecoratorPage(Composite parent) {
 		Composite imageGroup = SWTUtils.createHVFillComposite(parent,
 				SWTUtils.MARGINS_DEFAULT, 2);
 
+		showTracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowTracked);
+		showUntracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowUntracked);
+
 		return imageGroup;
 	}
 
@@ -241,6 +259,9 @@ public void handleEvent(Event event) {
 	private void initializeValues() {
 		final IPreferenceStore store = getPreferenceStore();
 
+		computeDeepDirtyState.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+
 		fileTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
 		folderTextFormat.setText(store
@@ -248,8 +269,20 @@ private void initializeValues() {
 		projectTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
 
-		showDirty.setSelection(store
-				.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		showTracked.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON));
+		showUntracked.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
+
+		SelectionListener selectionListener = new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				preview.refresh();
+			}
+		};
+
+		computeDeepDirtyState.addSelectionListener(selectionListener);
+		showTracked.addSelectionListener(selectionListener);
+		showUntracked.addSelectionListener(selectionListener);
 
 		setValid(true);
 	}
@@ -287,6 +320,9 @@ public boolean performOk() {
 	 */
 	private boolean performOk(IPreferenceStore store) {
 
+		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY,
+				computeDeepDirtyState.getSelection());
+
 		store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				fileTextFormat.getText());
 		store.setValue(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION,
@@ -294,8 +330,10 @@ private boolean performOk(IPreferenceStore store) {
 		store.setValue(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION,
 				projectTextFormat.getText());
 
-		store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY, showDirty
+		store.setValue(UIPreferences.DECORATOR_SHOW_TRACKED_ICON, showTracked
 				.getSelection());
+		store.setValue(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON,
+				showUntracked.getSelection());
 
 		return true;
 	}
@@ -308,6 +346,9 @@ protected void performDefaults() {
 		super.performDefaults();
 		IPreferenceStore store = getPreferenceStore();
 
+		computeDeepDirtyState.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+
 		fileTextFormat.setText(store
 				.getDefaultString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
 		folderTextFormat
@@ -317,8 +358,11 @@ protected void performDefaults() {
 				.setText(store
 						.getDefaultString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION));
 
-		showDirty.setSelection(store
-				.getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY));
+		showTracked.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON));
+		showUntracked
+				.setSelection(store
+						.getDefaultBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON));
 	}
 
 	/**
@@ -602,19 +646,26 @@ private PreviewDecoration getDecoration(Object element) {
 	}
 
 	private static class PreviewResource implements IDecoratableResource {
-		public final String name;
+		private final String name;
+
+		private final String branch;
 
-		public final String branch;
+		private final int type;
 
-		public final int type;
+		private Collection children;
 
-		public Collection children;
+		private boolean tracked;
 
-		public PreviewResource(String name, int type, String branch) {
+		private boolean ignored;
+
+		public PreviewResource(String name, int type, String branch,
+				boolean tracked, boolean ignored) {
 			this.name = name;
 			this.branch = branch;
 			this.type = type;
 			this.children = Collections.EMPTY_LIST;
+			this.tracked = tracked;
+			this.ignored = ignored;
 		}
 
 		public String getName() {
@@ -628,6 +679,14 @@ public int getType() {
 		public String getBranch() {
 			return branch;
 		}
+
+		public boolean isTracked() {
+			return tracked;
+		}
+
+		public boolean isIgnored() {
+			return ignored;
+		}
 	}
 
 	private class PreviewDecoration implements IDecoration {
diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
index d050be9..9940177 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties
@@ -369,4 +369,6 @@ DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and
 DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format:
 DecoratorPreferencesPage_textLabel=T&ext Decorations
 DecoratorPreferencesPage_iconLabel=&Icon Decorations
+DecoratorPreferencesPage_iconsShowTracked=Tracked resources
+DecoratorPreferencesPage_iconsShowUntracked=Untracked resources
 
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [EGIT PATCH v2 12/12] Implement label decorations for folders and projects
  2009-02-11 18:40                       ` [EGIT PATCH v2 12/12] Implement label decorations for folders and projects Tor Arne Vestbø
@ 2009-02-12  0:02                         ` Robin Rosenberg
  0 siblings, 0 replies; 21+ messages in thread
From: Robin Rosenberg @ 2009-02-12  0:02 UTC (permalink / raw)
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git


Since this is a large change, we can wait a day or so before pushing to master. This and
Shawn locking patch is in the update site on www.jgit.org (tentative builds) for those that want
to experience it without building. The version is called 0.4.0.200902120052.

-- robin

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [EGIT PATCH v2 00/12] Support customizable label decorations
  2009-02-11 18:40 [EGIT PATCH v2 00/12] Support customizable label decorations Tor Arne Vestbø
  2009-02-11 18:40 ` [EGIT PATCH v2 01/12] Add support code to handle plugin property changes Tor Arne Vestbø
@ 2009-02-16 20:57 ` Robin Rosenberg
  2009-02-16 22:49   ` Tor Arne Vestbø
  1 sibling, 1 reply; 21+ messages in thread
From: Robin Rosenberg @ 2009-02-16 20:57 UTC (permalink / raw)
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git

onsdag 11 februari 2009 19:40:02 skrev Tor Arne Vestbø <torarnv@gmail.com>:
> Known issues are:
> 
>   - If a project has a repository more than one level above the
>     project directory decorations will fail.

That one *is* annoying. My biggest projects don't get decorations now...

-- robin

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [EGIT PATCH v2 00/12] Support customizable label decorations
  2009-02-16 20:57 ` [EGIT PATCH v2 00/12] Support customizable label decorations Robin Rosenberg
@ 2009-02-16 22:49   ` Tor Arne Vestbø
  2009-02-17  5:52     ` Robin Rosenberg
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-16 22:49 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> onsdag 11 februari 2009 19:40:02 skrev Tor Arne Vestbø <torarnv@gmail.com>:
>> Known issues are:
>>
>>   - If a project has a repository more than one level above the
>>     project directory decorations will fail.
> 
> That one *is* annoying. My biggest projects don't get decorations now...

Ouch. I thought that was a rather rare situation, given that we only let
users create new repos either in the project or the project parent.

I'll whip something up :)

How has the series held up this past week in the update manager? Any
complaints from users or other things I should be aware of?

Tor Arne

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [EGIT PATCH v2 00/12] Support customizable label decorations
  2009-02-16 22:49   ` Tor Arne Vestbø
@ 2009-02-17  5:52     ` Robin Rosenberg
  2009-02-17 17:51       ` [EGIT PATCH 13/12] Add new file tree iterator that can adapt into a ContainerTreeIterator Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Robin Rosenberg @ 2009-02-17  5:52 UTC (permalink / raw)
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git

måndag 16 februari 2009 23:49:59 skrev Tor Arne Vestbø <torarnv@gmail.com>:
> Robin Rosenberg wrote:
> > onsdag 11 februari 2009 19:40:02 skrev Tor Arne Vestbø <torarnv@gmail.com>:
> >> Known issues are:
> >>
> >>   - If a project has a repository more than one level above the
> >>     project directory decorations will fail.
> > 
> > That one *is* annoying. My biggest projects don't get decorations now...
> 
> Ouch. I thought that was a rather rare situation, given that we only let
> users create new repos either in the project or the project parent.
Legacy, but some reasons make sense.

> I'll whip something up :)
> 
> How has the series held up this past week in the update manager? Any
642 or 461 downloads (depending on how you count). It's hard to say if different
if some downloads are from multiple or single users.

> complaints from users or other things I should be aware of?
One :)

-- robin

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [EGIT PATCH 13/12] Add new file tree iterator that can adapt into a ContainerTreeIterator
  2009-02-17  5:52     ` Robin Rosenberg
@ 2009-02-17 17:51       ` Tor Arne Vestbø
  2009-02-17 17:52         ` [EGIT PATCH 14/12] Allow project decorations regardless of repository root location Tor Arne Vestbø
  0 siblings, 1 reply; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-17 17:51 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

As the iterator recurses into subtrees it will look for directories
that can be mapped using a given workspace root, and if there's a
mapping it will return a ContainerTreeIterator instead.

This feature required exposing the underlying file of the FileEntry,
as well as making ContainerTreeIterator's constructor that takes a
parent iterator public.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../core/T0003_AdaptableFileTreeIteratorTest.java  |   74 +++++++++++++++++
 .../egit/core/AdaptableFileTreeIterator.java       |   87 ++++++++++++++++++++
 .../spearce/egit/core/ContainerTreeIterator.java   |   17 ++++-
 .../spearce/jgit/treewalk/FileTreeIterator.java    |   14 +++-
 4 files changed, 190 insertions(+), 2 deletions(-)
 create mode 100644 org.spearce.egit.core.test/src/org/spearce/egit/core/T0003_AdaptableFileTreeIteratorTest.java
 create mode 100644 org.spearce.egit.core/src/org/spearce/egit/core/AdaptableFileTreeIterator.java

diff --git a/org.spearce.egit.core.test/src/org/spearce/egit/core/T0003_AdaptableFileTreeIteratorTest.java b/org.spearce.egit.core.test/src/org/spearce/egit/core/T0003_AdaptableFileTreeIteratorTest.java
new file mode 100644
index 0000000..1e2fe03
--- /dev/null
+++ b/org.spearce.egit.core.test/src/org/spearce/egit/core/T0003_AdaptableFileTreeIteratorTest.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+package org.spearce.egit.core;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.spearce.egit.core.op.ConnectProviderOperation;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.egit.core.test.GitTestCase;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.WorkingTreeIterator;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
+
+public class T0003_AdaptableFileTreeIteratorTest extends GitTestCase {
+
+	private Repository repository;
+
+	private File repositoryRoot;
+
+	private File file;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		repository = new Repository(gitDir);
+		repositoryRoot = repository.getWorkDir();
+		repository.create();
+
+		file = new File(project.getProject().getLocation().toFile(), "a.txt");
+		final FileWriter fileWriter = new FileWriter(file);
+		fileWriter.write("aaaaaaaaaaa");
+		fileWriter.close();
+
+		final ConnectProviderOperation operation = new ConnectProviderOperation(
+				project.getProject(), null);
+		operation.run(null);
+	}
+
+	public void testFileTreeToContainerAdaptation() throws IOException {
+		final IWorkspaceRoot root = project.getProject().getWorkspace()
+				.getRoot();
+
+		final TreeWalk treeWalk = new TreeWalk(repository);
+		treeWalk.addTree(new AdaptableFileTreeIterator(repositoryRoot, root));
+		treeWalk.setRecursive(true);
+
+		final IFile eclipseFile = project.getProject().getFile(file.getName());
+		final RepositoryMapping mapping = RepositoryMapping
+				.getMapping(eclipseFile);
+		final Set<String> repositoryPaths = Collections.singleton(mapping
+				.getRepoRelativePath(eclipseFile));
+
+		assertTrue(repositoryPaths.size() == 1);
+		treeWalk.setFilter(PathFilterGroup.createFromStrings(repositoryPaths));
+
+		assertTrue(treeWalk.next());
+
+		final WorkingTreeIterator iterator = treeWalk.getTree(1,
+				WorkingTreeIterator.class);
+		assertTrue(iterator instanceof ContainerTreeIterator);
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/AdaptableFileTreeIterator.java b/org.spearce.egit.core/src/org/spearce/egit/core/AdaptableFileTreeIterator.java
new file mode 100644
index 0000000..61211f6
--- /dev/null
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/AdaptableFileTreeIterator.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (C) 2009, Tor Arne Vestbø <torarnv@gmail.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * See LICENSE for the full license text, also available.
+ *******************************************************************************/
+
+package org.spearce.egit.core;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.Path;
+import org.spearce.jgit.errors.IncorrectObjectTypeException;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.treewalk.AbstractTreeIterator;
+import org.spearce.jgit.treewalk.FileTreeIterator;
+
+/**
+ * Java IO file tree iterator that can adapt to a {@link ContainerTreeIterator}
+ * <p>
+ * The iterator automatically adapts to a {@link ContainerTreeIterator} when
+ * recursing into directories that are accessible from the given workspace root.
+ *
+ * @see org.spearce.jgit.treewalk.FileTreeIterator
+ * @see org.spearce.egit.core.ContainerTreeIterator
+ */
+public class AdaptableFileTreeIterator extends FileTreeIterator {
+
+	IWorkspaceRoot root;
+
+	/**
+	 * Create a new iterator to traverse the given directory and its children
+	 * <p>
+	 * The iterator will automatically adapt to a {@link ContainerTreeIterator}
+	 * when encountering directories what can be mapped into the given workspace
+	 * root.
+	 *
+	 * @param path
+	 *            the starting directory. This directory should correspond to
+	 *            the repository root.
+	 * @param workspaceRoot
+	 *            the workspace root to check resource mapping against.
+	 *
+	 */
+	public AdaptableFileTreeIterator(final File path,
+			final IWorkspaceRoot workspaceRoot) {
+		super(path);
+		root = workspaceRoot;
+	}
+
+	/**
+	 * Create a new iterator to traverse a subdirectory.
+	 * <p>
+	 * The iterator will automatically adapt to a {@link ContainerTreeIterator}
+	 * when encountering directories what can be mapped into the given workspace
+	 * root.
+	 *
+	 * @param path
+	 *            the subdirectory. This should be a directory contained within
+	 *            the parent directory.
+	 * @param parent
+	 *            the parent iterator we were created from.
+	 * @param workspaceRoot
+	 *            the workspace root to check resource mapping against.
+	 */
+	protected AdaptableFileTreeIterator(final AdaptableFileTreeIterator parent,
+			File path, final IWorkspaceRoot workspaceRoot) {
+		super(parent, path);
+		root = workspaceRoot;
+	}
+
+	@Override
+	public AbstractTreeIterator createSubtreeIterator(Repository repo)
+			throws IncorrectObjectTypeException, IOException {
+		final File currentFile = ((FileEntry) current()).getFile();
+		final IContainer[] containers = root.findContainersForLocation(new Path(
+				currentFile.getAbsolutePath()));
+		if (containers.length > 0)
+			return new ContainerTreeIterator(this, containers[0]);
+		else
+			return new AdaptableFileTreeIterator(this, currentFile, root);
+	}
+}
diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java b/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
index 2403252..de592aa 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/ContainerTreeIterator.java
@@ -88,7 +88,22 @@ public ContainerTreeIterator(final IWorkspaceRoot root) {
 		init(entries());
 	}
 
-	private ContainerTreeIterator(final WorkingTreeIterator p,
+	/**
+	 * Construct a new iterator from a container in the workspace, with a given
+	 * parent iterator.
+	 * <p>
+	 * The iterator will support traversal over the named container, but only if
+	 * it is contained within a project which has the Git repository provider
+	 * connected and this resource is mapped into a Git repository. During the
+	 * iteration the paths will be automatically generated to match the proper
+	 * repository paths for this container's children.
+	 * 
+	 * @param p
+	 *            the parent iterator we were created from.
+	 * @param base
+	 *            the part of the workspace the iterator will walk over.
+	 */
+	public ContainerTreeIterator(final WorkingTreeIterator p,
 			final IContainer base) {
 		super(p);
 		node = base;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/FileTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/FileTreeIterator.java
index 2c71151..ef9866a 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/FileTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/FileTreeIterator.java
@@ -100,7 +100,10 @@ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
 		return r;
 	}
 
-	static class FileEntry extends Entry {
+	/**
+	 * Wrapper for a standard Java IO file
+	 */
+	static public class FileEntry extends Entry {
 		final File file;
 
 		private final FileMode mode;
@@ -151,5 +154,14 @@ public long getLastModified() {
 		public InputStream openInputStream() throws IOException {
 			return new FileInputStream(file);
 		}
+
+		/**
+		 * Get the underlying file of this entry.
+		 * 
+		 * @return the underlying file of this entry
+		 */
+		public File getFile() {
+			return file;
+		}
 	}
 }
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [EGIT PATCH 14/12] Allow project decorations regardless of repository root location
  2009-02-17 17:51       ` [EGIT PATCH 13/12] Add new file tree iterator that can adapt into a ContainerTreeIterator Tor Arne Vestbø
@ 2009-02-17 17:52         ` Tor Arne Vestbø
  0 siblings, 0 replies; 21+ messages in thread
From: Tor Arne Vestbø @ 2009-02-17 17:52 UTC (permalink / raw)
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

If the repository root is more than one level above the project
directory we use the AdaptableFileTreeIterator to recurse the
repository tree until we find a directory that can be mapped
to a container in the workspace. The iterator is then adapted to
a ContainerTreeIterator and decorations are applied like usual.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../decorators/DecoratableResourceAdapter.java     |   39 +++++++++++++-------
 1 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
index e2fe54b..5c68d5b 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/DecoratableResourceAdapter.java
@@ -13,15 +13,17 @@
 
 package org.spearce.egit.ui.internal.decorators;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Set;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.team.core.Team;
+import org.spearce.egit.core.AdaptableFileTreeIterator;
 import org.spearce.egit.core.ContainerTreeIterator;
 import org.spearce.egit.core.ContainerTreeIterator.ResourceEntry;
 import org.spearce.egit.core.project.RepositoryMapping;
@@ -39,6 +41,7 @@
 import org.spearce.jgit.revwalk.RevWalk;
 import org.spearce.jgit.treewalk.EmptyTreeIterator;
 import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.WorkingTreeIterator;
 import org.spearce.jgit.treewalk.filter.AndTreeFilter;
 import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 import org.spearce.jgit.treewalk.filter.TreeFilter;
@@ -210,13 +213,21 @@ public boolean include(TreeWalk treeWalk)
 		}
 
 		private boolean shouldRecurse(TreeWalk treeWalk) {
-			final ContainerTreeIterator workspaceIterator = treeWalk.getTree(
-					T_WORKSPACE, ContainerTreeIterator.class);
-			final ResourceEntry resourceEntry = workspaceIterator != null ? workspaceIterator
-					.getResourceEntry()
-					: null;
-			IResource visitingResource = resourceEntry.getResource();
+			final WorkingTreeIterator workspaceIterator = treeWalk.getTree(
+					T_WORKSPACE, WorkingTreeIterator.class);
+
+			if (workspaceIterator instanceof AdaptableFileTreeIterator)
+				return true;
+
+			ResourceEntry resourceEntry = null;
+			if (workspaceIterator != null)
+				resourceEntry = ((ContainerTreeIterator) workspaceIterator)
+						.getResourceEntry();
+
+			if (resourceEntry == null)
+				return true;
 
+			IResource visitingResource = resourceEntry.getResource();
 			if (targetDepth == -1) {
 				if (visitingResource.equals(resource)
 						|| visitingResource.getParent().equals(resource))
@@ -319,14 +330,16 @@ private TreeWalk createThreeWayTreeWalk() throws IOException {
 
 		// Working directory
 		IProject project = resource.getProject();
-		IWorkspace workspace = resource.getWorkspace();
-		if (repository.getWorkDir().equals(project.getLocation().toFile()))
+		IWorkspaceRoot workspaceRoot = resource.getWorkspace().getRoot();
+		File repoRoot = repository.getWorkDir();
+
+		if (repoRoot.equals(project.getLocation().toFile()))
 			treeWalk.addTree(new ContainerTreeIterator(project));
+		else if (repoRoot.equals(workspaceRoot.getLocation().toFile()))
+			treeWalk.addTree(new ContainerTreeIterator(workspaceRoot));
 		else
-			treeWalk.addTree(new ContainerTreeIterator(workspace.getRoot()));
-
-		// TODO: Add fallback for projects with the repository more than
-		// one parent up, for example by using a stack of DummyIterators
+			treeWalk.addTree(new AdaptableFileTreeIterator(repoRoot,
+					workspaceRoot));
 
 		return treeWalk;
 	}
-- 
1.6.1.2.309.g2ea3

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2009-02-17 17:53 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-02-11 18:40 [EGIT PATCH v2 00/12] Support customizable label decorations Tor Arne Vestbø
2009-02-11 18:40 ` [EGIT PATCH v2 01/12] Add support code to handle plugin property changes Tor Arne Vestbø
2009-02-11 18:40   ` [EGIT PATCH v2 02/12] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
2009-02-11 18:40     ` [EGIT PATCH v2 03/12] Add a specialized team exception for Git Tor Arne Vestbø
2009-02-11 18:40       ` [EGIT PATCH v2 04/12] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
2009-02-11 18:40         ` [EGIT PATCH v2 05/12] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
2009-02-11 18:40           ` [EGIT PATCH v2 06/12] Implement basic customizable label decorations with preferences Tor Arne Vestbø
2009-02-11 18:40             ` [EGIT PATCH v2 07/12] Add binding for name of the current branch Tor Arne Vestbø
2009-02-11 18:40               ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
2009-02-11 18:40                 ` [EGIT PATCH v2 09/12] Implement icon and text decorations of various resource states Tor Arne Vestbø
2009-02-11 18:40                   ` [EGIT PATCH v2 10/12] Don't decorate every single resource on repository change Tor Arne Vestbø
2009-02-11 18:40                     ` [EGIT PATCH v2 11/12] Expose the underlying resource entries in ContainerTreeIterator Tor Arne Vestbø
2009-02-11 18:40                       ` [EGIT PATCH v2 12/12] Implement label decorations for folders and projects Tor Arne Vestbø
2009-02-12  0:02                         ` Robin Rosenberg
2009-02-11 22:16                 ` [EGIT PATCH v2 08/12] Add icon decoration for tracked and untracked resources Robin Rosenberg
2009-02-11 22:46                   ` [EGIT PATCH 08/12 v3] " Tor Arne Vestbø
2009-02-16 20:57 ` [EGIT PATCH v2 00/12] Support customizable label decorations Robin Rosenberg
2009-02-16 22:49   ` Tor Arne Vestbø
2009-02-17  5:52     ` Robin Rosenberg
2009-02-17 17:51       ` [EGIT PATCH 13/12] Add new file tree iterator that can adapt into a ContainerTreeIterator Tor Arne Vestbø
2009-02-17 17:52         ` [EGIT PATCH 14/12] Allow project decorations regardless of repository root location Tor Arne Vestbø

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.