git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [EGIT PATCH 00/11] Support customizable label decorations
@ 2009-02-05  1:00 Tor Arne Vestbø
  2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
                   ` (2 more replies)
  0 siblings, 3 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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.

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, 
'Inspect dirty state...', controls whether decoration of container
elements such as projects and folders should traverse child elements
to decide if the container is dirty.

Disabling these options will improve performance for large trees.

The code should be solid enough for normal use, but I may have
missed situations that the code does not handle -- resuling in
crash and burn. 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 parent package in the
    package hierarcy will appear dirty, even when the layout is
    set to 'flat'.

I've 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

PS: This is my first major patch to EGit, so apologies in advance
if I messed up the steps of the submit process in any way :)


Tor Arne Vestbø (11):
  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 decorations of dirty, staged, and conflicting resources
  Don't decorate every single resource on repository change
  Implement label decorations for folders and projects

 org.spearce.egit.core/META-INF/MANIFEST.MF         |    5 +-
 .../src/org/spearce/egit/core/GitException.java    |  168 +++
 .../core/internal/util/ExceptionCollector.java     |  128 +++
 .../spearce/egit/core/project/GitProjectData.java  |   33 +-
 org.spearce.egit.ui/.options                       |    8 +-
 org.spearce.egit.ui/icons/ovr/assumevalid.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/conflict.gif         |  Bin 64 -> 164 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 -> 114 bytes
 org.spearce.egit.ui/icons/ovr/staged_removed.gif   |  Bin 0 -> 114 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         |   70 ++-
 .../egit/ui/PluginPreferenceInitializer.java       |   13 +
 .../src/org/spearce/egit/ui/UIIcons.java           |   19 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |   19 +
 .../src/org/spearce/egit/ui/UIText.java            |   87 ++-
 .../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/GitLightweightDecorator.java        | 1062 ++++++++++++++++++++
 .../internal/decorators/GitResourceDecorator.java  |  454 ---------
 .../internal/decorators/IDecoratableResource.java  |   93 ++
 .../preferences/GitDecoratorPreferencePage.java    |  911 +++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   35 +-
 30 files changed, 3230 insertions(+), 495 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
 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/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] 31+ messages in thread

* [EGIT PATCH 01/11] Add support code to handle plugin property changes
  2009-02-05  1:00 [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
@ 2009-02-05  1:00 ` Tor Arne Vestbø
  2009-02-05  1:00   ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
  2009-02-05 15:53   ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Shawn O. Pearce
  2009-02-05  1:04 ` [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
  2009-02-05 16:06 ` Shawn O. Pearce
  2 siblings, 2 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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..d4a9e8e 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 void addPropertyChangeListener(
+			IPropertyChangeListener listener) {
+		propertyChangeListeners.add(listener);
+	}
+
+	/**
+	 * Remove a Team property changes.
+	 * 
+	 * @param listener
+	 *            The listener to remove
+	 */
+	public static void removePropertyChangeListener(
+			IPropertyChangeListener listener) {
+		propertyChangeListeners.remove(listener);
+	}
+
+	/**
+	 * Broadcast a Team property change.
+	 * 
+	 * @param event
+	 *            The event to broadcast
+	 */
+	public static 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] 31+ messages in thread

* [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners
  2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
@ 2009-02-05  1:00   ` Tor Arne Vestbø
  2009-02-05  1:00     ` [EGIT PATCH 03/11] Add a specialized team exception for Git Tor Arne Vestbø
  2009-02-05 15:48     ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Shawn O. Pearce
  2009-02-05 15:53   ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Shawn O. Pearce
  1 sibling, 2 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 UTC (permalink / raw
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Also, add method for removing listeners, and remove unused
private method for getting the current listeners.

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 .../spearce/egit/core/project/GitProjectData.java  |   33 +++++++++-----------
 1 files changed, 15 insertions(+), 18 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..4ca4f8e 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,8 @@ 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);
-	}
-
-	private static synchronized RepositoryChangeListener[] getRepositoryChangeListeners() {
-		return repositoryChangeListeners;
+		for (RepositoryChangeListener listener : repositoryChangeListeners)
+			listener.repositoryChanged(which);
 	}
 
 	/**
-- 
1.6.1.2.309.g2ea3

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

* [EGIT PATCH 03/11] Add a specialized team exception for Git
  2009-02-05  1:00   ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
@ 2009-02-05  1:00     ` Tor Arne Vestbø
  2009-02-05  1:00       ` [EGIT PATCH 04/11] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
  2009-02-05 15:48     ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Shawn O. Pearce
  1 sibling, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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] 31+ messages in thread

* [EGIT PATCH 04/11] Add new class ExceptionCollector for grouping exceptions
  2009-02-05  1:00     ` [EGIT PATCH 03/11] Add a specialized team exception for Git Tor Arne Vestbø
@ 2009-02-05  1:00       ` Tor Arne Vestbø
  2009-02-05  1:00         ` [EGIT PATCH 05/11] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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] 31+ messages in thread

* [EGIT PATCH 05/11] Add new class SWTUtils with helper-methods for creating controls
  2009-02-05  1:00       ` [EGIT PATCH 04/11] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
@ 2009-02-05  1:00         ` Tor Arne Vestbø
  2009-02-05  1:00           ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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] 31+ messages in thread

* [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05  1:00         ` [EGIT PATCH 05/11] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
@ 2009-02-05  1:00           ` Tor Arne Vestbø
  2009-02-05  1:00             ` [EGIT PATCH 07/11] Add binding for name of the current branch Tor Arne Vestbø
                               ` (2 more replies)
  0 siblings, 3 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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    |  735 ++++++++++++++++++++
 .../src/org/spearce/egit/ui/uitext.properties      |   25 +-
 14 files changed, 1438 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 d4a9e8e..9d03c70 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 7a7d3ef..23498c8 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;
 
 	/** */
@@ -913,6 +910,66 @@
 	/** */
 	public static String BranchSelectionDialog_Refs;
 
+	/** */
+	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..2ef0292
--- /dev/null
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java
@@ -0,0 +1,735 @@
+/*******************************************************************************
+ * 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.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.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 fPreview;
+
+	private static final Collection PREVIEW_FILESYSTEM_ROOT;
+
+	private static ThemeListener fThemeListener;
+
+	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 headerGroup = new Composite(parent, SWT.NULL);
+		GridLayout layout = new GridLayout();
+		layout.marginWidth = 0;
+		layout.marginHeight = 0;
+		layout.verticalSpacing = 10;
+		headerGroup.setLayout(layout);
+		GridData data = new GridData();
+		data.horizontalAlignment = GridData.FILL;
+		headerGroup.setLayoutData(data);
+
+		SWTUtils.createPreferenceLink(
+				(IWorkbenchPreferenceContainer) getContainer(), headerGroup,
+				"org.eclipse.ui.preferencePages.Decorators",
+				UIText.DecoratorPreferencesPage_labelDecorationsLink); //$NON-NLS-1$
+
+		SWTUtils.createLabel(headerGroup,
+				UIText.DecoratorPreferencesPage_decorationSettings);
+
+		TabFolder tabFolder = new TabFolder(parent, SWT.NONE);
+		GridData tabData = SWTUtils.createHVFillGridData();
+		tabData.heightHint = 100;
+		tabFolder.setLayoutData(tabData);
+
+		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();
+
+		fPreview = new Preview(parent);
+		fPreview.refresh();
+
+		// TODO: Add help text for this preference page
+
+		fThemeListener = new ThemeListener(fPreview);
+		PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(
+				fThemeListener);
+		Dialog.applyDialogFont(parent);
+		return tabFolder;
+	}
+
+	private Control createGeneralDecoratorPage(Composite parent) {
+		Composite composite = new Composite(parent, SWT.NULL);
+
+		GridLayout layout = new GridLayout();
+		composite.setLayout(layout);
+		GridData data = new GridData();
+		data.horizontalAlignment = GridData.FILL;
+		composite.setLayoutData(data);
+
+		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 = new Composite(parent, SWT.NULL);
+		GridLayout layout = new GridLayout();
+		layout.numColumns = 3;
+		fileTextGroup.setLayout(layout);
+		GridData data = new GridData();
+		data.horizontalAlignment = GridData.FILL;
+		fileTextGroup.setLayoutData(data);
+
+		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 = new Composite(parent, SWT.NULL);
+		GridLayout layout = new GridLayout();
+		imageGroup.setLayout(layout);
+		GridData data = new GridData();
+		data.horizontalAlignment = GridData.FILL;
+		imageGroup.setLayoutData(data);
+
+		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(fThemeListener);
+		super.dispose();
+	}
+
+	private static class ThemeListener implements IPropertyChangeListener {
+		private final Preview preview;
+
+		ThemeListener(Preview preview) {
+			this.preview = preview;
+		}
+
+		public void propertyChange(PropertyChangeEvent event) {
+			preview.refresh();
+		}
+	}
+
+	/**
+	 * 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 (fPreview != null)
+			fPreview.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);
+			GridData previewGridData = SWTUtils.createHVFillGridData();
+			previewGridData.heightHint = 100;
+			fViewer.getControl().setLayoutData(previewGridData);
+			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 a86e58b..4a0a387 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
@@ -340,3 +339,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] 31+ messages in thread

* [EGIT PATCH 07/11] Add binding for name of the current branch
  2009-02-05  1:00           ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Tor Arne Vestbø
@ 2009-02-05  1:00             ` Tor Arne Vestbø
  2009-02-05  1:00               ` [EGIT PATCH 08/11] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
  2009-02-05 20:02             ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Robin Rosenberg
  2009-02-05 20:04             ` Robin Rosenberg
  2 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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 23498c8..f939558 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
@@ -956,6 +956,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 2ef0292..2a1a3a8 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
@@ -94,10 +94,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);
 	}
@@ -488,6 +488,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;
 	}
 
@@ -510,8 +512,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
@@ -635,12 +637,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;
 		}
@@ -652,6 +657,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 4a0a387..58cb9e6 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
@@ -354,10 +354,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] 31+ messages in thread

* [EGIT PATCH 08/11] Add icon decoration for tracked and untracked resources
  2009-02-05  1:00             ` [EGIT PATCH 07/11] Add binding for name of the current branch Tor Arne Vestbø
@ 2009-02-05  1:00               ` Tor Arne Vestbø
  2009-02-05  1:00                 ` [EGIT PATCH 09/11] Implement decorations of dirty, staged, and conflicting resources Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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 1697542..0cead29 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;
@@ -77,7 +77,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 f939558..fe62d0a 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
@@ -973,6 +973,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 2a1a3a8..2cbf07a 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;
@@ -84,7 +87,11 @@
 
 	private Text projectTextFormat;
 
-	private Button showDirty;
+	private Button computeDeepDirtyState;
+
+	private Button showTracked;
+
+	private Button showUntracked;
 
 	private Preview fPreview;
 
@@ -94,10 +101,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);
 	}
@@ -171,7 +184,7 @@ private Control createGeneralDecoratorPage(Composite parent) {
 		data.horizontalAlignment = GridData.FILL;
 		composite.setLayoutData(data);
 
-		showDirty = SWTUtils.createCheckBox(composite,
+		computeDeepDirtyState = SWTUtils.createCheckBox(composite,
 				UIText.DecoratorPreferencesPage_computeDeep);
 
 		return composite;
@@ -222,6 +235,11 @@ private Control createIconDecoratorPage(Composite parent) {
 		data.horizontalAlignment = GridData.FILL;
 		imageGroup.setLayoutData(data);
 
+		showTracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowTracked);
+		showUntracked = SWTUtils.createCheckBox(imageGroup,
+				UIText.DecoratorPreferencesPage_iconsShowUntracked);
+
 		return imageGroup;
 	}
 
@@ -261,6 +279,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
@@ -268,8 +289,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);
 	}
@@ -307,6 +340,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,
@@ -314,8 +350,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;
 	}
@@ -328,6 +366,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
@@ -337,8 +378,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));
 	}
 
 	/**
@@ -635,19 +679,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() {
@@ -661,6 +712,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 58cb9e6..05fdba9 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
@@ -363,4 +363,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] 31+ messages in thread

* [EGIT PATCH 09/11] Implement decorations of dirty, staged, and conflicting resources
  2009-02-05  1:00               ` [EGIT PATCH 08/11] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
@ 2009-02-05  1:00                 ` Tor Arne Vestbø
  2009-02-05  1:00                   ` [EGIT PATCH 10/11] Don't decorate every single resource on repository change Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 UTC (permalink / raw
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.ui/icons/ovr/assumevalid.gif      |  Bin 64 -> 0 bytes
 org.spearce.egit.ui/icons/ovr/conflict.gif         |  Bin 64 -> 164 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 -> 114 bytes
 org.spearce.egit.ui/icons/ovr/staged_removed.gif   |  Bin 0 -> 114 bytes
 .../egit/ui/PluginPreferenceInitializer.java       |    2 +
 .../src/org/spearce/egit/ui/UIIcons.java           |   13 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    4 +
 .../src/org/spearce/egit/ui/UIText.java            |   14 +-
 .../decorators/GitLightweightDecorator.java        |  284 ++++++++++++++++----
 .../internal/decorators/IDecoratableResource.java  |   39 +++
 .../preferences/GitDecoratorPreferencePage.java    |  131 ++++++++--
 .../src/org/spearce/egit/ui/uitext.properties      |   14 +-
 15 files changed, 427 insertions(+), 74 deletions(-)
 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/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..186345711bb5443b440c4265fe62b099cce5def0 100644
GIT binary patch
literal 164
zcmZ?wbhEHbWM|-DIKseS;8EwAJi|S8rcc&9znlfG$<y4^W_f1J^~{*<m%Cuej;o77
z=*sb{&yHVx`snqS|6ssCG*JA>!pOiN$e;t#0kV^URWU)OPmsZ3MW&ETlG9;j{ud8T
Wc$>JmHcJQuD+C104Qdczum%A1Q!l9i

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..f2b87e274667c441897d0853e6a437ba5487f51c
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{QC}7o_(m`FiYy3
RK~byn9^Qv66>=^N)&TbU9;*NV

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..b6b4911d9eba72838d54fbcf6109eaf3836bf0db
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{QC~|wl_0qWLX?O
RA=-DKlJ_A?g`5k6H304c9%KLj

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..ef886cf 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,8 @@ 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);
 
 		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 0cead29..302637e 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;
@@ -75,8 +79,9 @@
 
 	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");
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..409b335 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,10 @@
 	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";
 
 	/**
 	 * 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 fe62d0a..b62ca4c 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
@@ -953,12 +953,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;
 
 	/** */
@@ -979,6 +985,12 @@
 	/** */
 	public static String DecoratorPreferencesPage_iconsShowUntracked;
 
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowStaged;
+
+	/** */
+	public static String DecoratorPreferencesPage_iconsShowConflicts;
+
 	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..aa6a261 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,18 @@ public void decorate(Object element, IDecoration decoration) {
 
 		private boolean ignored = false;
 
+		private boolean dirty = false;
+
+		private boolean conflicts = 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 +222,161 @@ 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;
+				} 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 +398,18 @@ public boolean isTracked() {
 		public boolean isIgnored() {
 			return ignored;
 		}
+
+		public boolean isDirty() {
+			return dirty;
+		}
+
+		public Staged staged() {
+			return staged;
+		}
+
+		public boolean hasConflicts() {
+			return conflicts;
+		}
 	}
 
 	/**
@@ -298,6 +427,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 +460,23 @@ public ImageData getImageData() {
 
 		private static ImageDescriptor untrackedImage;
 
+		private static ImageDescriptor stagedImage;
+
+		private static ImageDescriptor stagedAddedImage;
+
+		private static ImageDescriptor stagedRemovedImage;
+
+		private static ImageDescriptor conflictImage;
+
 		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);
 		}
 
 		/**
@@ -354,6 +502,9 @@ public DecorationHelper(IPreferenceStore preferencesStore) {
 		 */
 		public void decorate(IDecoration decoration,
 				IDecoratableResource resource) {
+			if (resource.isIgnored())
+				return;
+
 			decorateText(decoration, resource);
 			decorateIcons(decoration, resource);
 		}
@@ -379,22 +530,44 @@ 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;
+
+				// 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 +584,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 +599,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 +615,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 +704,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..d2b6b5c 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,27 @@
 	 * @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();
 }
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 2cbf07a..74bc209 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
@@ -56,6 +56,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;
@@ -72,8 +73,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
@@ -93,6 +95,10 @@
 
 	private Button showUntracked;
 
+	private Button showStaged;
+
+	private Button showConflicts;
+
 	private Preview fPreview;
 
 	private static final Collection PREVIEW_FILESYSTEM_ROOT;
@@ -101,16 +107,29 @@
 
 	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); //$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)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"tracked.txt", IResource.FILE, null, true, false, false, Staged.NOT_STAGED, 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)); //$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)); //$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)); //$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)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"partially-staged.txt", IResource.FILE, null, true, false, true, Staged.MODIFIED, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"added.txt", IResource.FILE, null, true, false, false, Staged.ADDED, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"removed.txt", IResource.FILE, null, true, false, false, Staged.REMOVED, false)); //$NON-NLS-1$
+		children.add(new PreviewResource(
+						"conflict.txt", IResource.FILE, null, true, false, true, Staged.NOT_STAGED, true)); //$NON-NLS-1$
 		project.children = children;
 		PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project);
 	}
@@ -179,6 +198,7 @@ private Control createGeneralDecoratorPage(Composite parent) {
 		Composite composite = new Composite(parent, SWT.NULL);
 
 		GridLayout layout = new GridLayout();
+		layout.marginHeight = 10;
 		composite.setLayout(layout);
 		GridData data = new GridData();
 		data.horizontalAlignment = GridData.FILL;
@@ -206,22 +226,29 @@ private Control createTextDecoratorPage(Composite parent) {
 		data.horizontalAlignment = GridData.FILL;
 		fileTextGroup.setLayoutData(data);
 
+		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;
@@ -230,6 +257,7 @@ private Control createTextDecoratorPage(Composite parent) {
 	private Control createIconDecoratorPage(Composite parent) {
 		Composite imageGroup = new Composite(parent, SWT.NULL);
 		GridLayout layout = new GridLayout();
+		layout.marginHeight = 10;
 		imageGroup.setLayout(layout);
 		GridData data = new GridData();
 		data.horizontalAlignment = GridData.FILL;
@@ -239,17 +267,27 @@ 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);
 
 		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();
@@ -293,6 +331,10 @@ 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));
 
 		SelectionListener selectionListener = new SelectionAdapter() {
 			public void widgetSelected(SelectionEvent e) {
@@ -303,6 +345,8 @@ public void widgetSelected(SelectionEvent e) {
 		computeDeepDirtyState.addSelectionListener(selectionListener);
 		showTracked.addSelectionListener(selectionListener);
 		showUntracked.addSelectionListener(selectionListener);
+		showStaged.addSelectionListener(selectionListener);
+		showConflicts.addSelectionListener(selectionListener);
 
 		setValid(true);
 	}
@@ -354,6 +398,10 @@ 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());
 
 		return true;
 	}
@@ -383,6 +431,11 @@ 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));
 	}
 
 	/**
@@ -505,7 +558,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;
 	}
 
@@ -518,7 +575,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;
 	}
 
@@ -531,7 +592,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;
@@ -691,14 +756,25 @@ private PreviewDecoration getDecoration(Object element) {
 
 		private boolean ignored;
 
+		private boolean dirty;
+
+		private boolean conflicts;
+
+		private Staged staged;
+
 		public PreviewResource(String name, int type, String branch,
-				boolean tracked, boolean ignored) {
+				boolean tracked, boolean ignored, boolean dirty, Staged staged,
+				boolean conflicts) {
+
 			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;
 		}
 
 		public String getName() {
@@ -720,6 +796,18 @@ public boolean isTracked() {
 		public boolean isIgnored() {
 			return ignored;
 		}
+
+		public boolean isDirty() {
+			return dirty;
+		}
+
+		public Staged staged() {
+			return staged;
+		}
+
+		public boolean hasConflicts() {
+			return conflicts;
+		}
 	}
 
 	private class PreviewDecoration implements IDecoration {
@@ -736,12 +824,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 05fdba9..2762d11 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
@@ -352,13 +352,15 @@ 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
@@ -366,3 +368,7 @@ DecoratorPreferencesPage_iconLabel=&Icon Decorations
 DecoratorPreferencesPage_iconsShowTracked=Tracked resources
 DecoratorPreferencesPage_iconsShowUntracked=Untracked resources
 
+Decorator_exceptionMessage=Errors occurred while applying Git decorations to resources.
+
+DecoratorPreferencesPage_iconsShowStaged=Staged resources
+DecoratorPreferencesPage_iconsShowConflicts=Conflicting resources
-- 
1.6.1.2.309.g2ea3

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

* [EGIT PATCH 10/11] Don't decorate every single resource on repository change
  2009-02-05  1:00                 ` [EGIT PATCH 09/11] Implement decorations of dirty, staged, and conflicting resources Tor Arne Vestbø
@ 2009-02-05  1:00                   ` Tor Arne Vestbø
  2009-02-05  1:00                     ` [EGIT PATCH 11/11] Implement label decorations for folders and projects Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 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 aa6a261..45b9f83 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;
@@ -682,7 +679,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));
 		}
 	}
 
@@ -809,29 +806,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] 31+ messages in thread

* [EGIT PATCH 11/11] Implement label decorations for folders and projects
  2009-02-05  1:00                   ` [EGIT PATCH 10/11] Don't decorate every single resource on repository change Tor Arne Vestbø
@ 2009-02-05  1:00                     ` Tor Arne Vestbø
  0 siblings, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:00 UTC (permalink / raw
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

The option "Inspect dirty state of children..." controls
if the decoration process should look at child resources
to decide if a container is dirty or not. For large/deep
projects this can be quite time consuming.

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

Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
 org.spearce.egit.ui/.options                       |    8 +-
 .../src/org/spearce/egit/ui/Activator.java         |    2 +-
 .../egit/ui/PluginPreferenceInitializer.java       |    3 +-
 .../src/org/spearce/egit/ui/UIPreferences.java     |    4 +-
 .../src/org/spearce/egit/ui/UIText.java            |    5 +-
 .../decorators/GitLightweightDecorator.java        |  344 +++++++++++++++-----
 .../preferences/GitDecoratorPreferencePage.java    |   33 ++-
 .../src/org/spearce/egit/ui/uitext.properties      |    3 +-
 8 files changed, 311 insertions(+), 91 deletions(-)

diff --git a/org.spearce.egit.ui/.options b/org.spearce.egit.ui/.options
index a084b35..8fc1c19 100644
--- a/org.spearce.egit.ui/.options
+++ b/org.spearce.egit.ui/.options
@@ -1 +1,7 @@
-org.spearce.egit.ui/trace/verbose = false
+# Debugging options for the org.spearce.egit.ui plugin.
+
+# Show general verbose output
+org.spearce.egit.ui/verbose = false
+
+# Show debug output for label decorations
+org.spearce.egit.ui/decorations = false
\ No newline at end of file
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 9d03c70..45010ce 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
@@ -190,7 +190,7 @@ public Activator() {
 
 	public void start(final BundleContext context) throws Exception {
 		super.start(context);
-		traceVerbose = isOptionSet("/trace/verbose");
+		traceVerbose = isOptionSet("/verbose");
 		setupSSH(context);
 		setupProxy(context);
 		setupRepoChangeScanner();
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 ef886cf..9af35bc 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,8 @@ 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_COMPUTE_DEEP_DIRTY, true);
 		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 409b335..f0a5e28 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_COMPUTE_DEEP_DIRTY = "decorator_compute_deep_dirty";
 	/** */
 	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 b62ca4c..5dd28df 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
@@ -920,7 +920,10 @@
 	public static String DecoratorPreferencesPage_addVariablesAction;
 
 	/** */
-	public static String DecoratorPreferencesPage_computeDeep;
+	public static String DecoratorPreferencesPage_recomputeAncestorDecorations;
+
+	/** */
+	public static String DecoratorPreferencesPage_computeDeepDirtyState;
 
 	/** */
 	public static String DecoratorPreferencesPage_description;
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 45b9f83..c72bfdb 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
@@ -31,6 +31,7 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.util.IPropertyChangeListener;
@@ -48,6 +49,7 @@
 import org.eclipse.team.ui.TeamUI;
 import org.eclipse.ui.IContributorResourceAdapter;
 import org.eclipse.ui.PlatformUI;
+import org.spearce.egit.core.ContainerTreeIterator;
 import org.spearce.egit.core.GitException;
 import org.spearce.egit.core.internal.util.ExceptionCollector;
 import org.spearce.egit.core.project.GitProjectData;
@@ -61,6 +63,8 @@
 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.IndexChangedEvent;
@@ -72,7 +76,11 @@
 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;
+import org.spearce.jgit.util.FS;
 
 /**
  * Supplies annotations for displayed resources
@@ -110,6 +118,34 @@
 			IStatus.ERROR, Activator.getDefault().getLog());
 
 	/**
+	 * Property constant indicating if tracing/debugging of decorations is
+	 * enabled
+	 */
+	private static boolean DEBUG_DECORATIONS = false;
+
+	static {
+		DEBUG_DECORATIONS = "true".equals(Platform.getDebugOption(Activator.getPluginId() + "/decorations")); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+	private static void debug(String what) {
+		StackTraceElement frame = Thread.currentThread().getStackTrace()[2];
+		if (frame.getMethodName().endsWith("access$1"))
+			frame = Thread.currentThread().getStackTrace()[3];
+		String className = frame.getClassName().substring(
+				frame.getClassName().lastIndexOf('.') + 1);
+		System.out.print(className + "::" + frame.getMethodName() + "(): ");
+
+		debug(what, 0);
+	}
+
+	private static void debug(String what, int numIndents) {
+		for (int i = 0; i < numIndents; ++i)
+			System.out.print("\t");
+
+		System.out.println(what);
+	}
+
+	/**
 	 * Constructs a new Git resource decorator
 	 */
 	public GitLightweightDecorator() {
@@ -174,6 +210,9 @@ public void decorate(Object element, IDecoration decoration) {
 		if (activator == null)
 			return;
 
+		if (DEBUG_DECORATIONS)
+			debug("deocrating '" + resource.getLocation() + "'");
+
 		try {
 			DecorationHelper helper = new DecorationHelper(activator
 					.getPreferenceStore());
@@ -184,7 +223,8 @@ public void decorate(Object element, IDecoration decoration) {
 		}
 	}
 
-	private class DecoratableResourceAdapter implements IDecoratableResource {
+	private static class DecoratableResourceAdapter implements
+			IDecoratableResource {
 
 		private final IResource resource;
 
@@ -212,6 +252,111 @@ public void decorate(Object element, IDecoration decoration) {
 
 		static final int T_WORKSPACE = 2;
 
+		private static class ContainerDiffFilter extends TreeFilter {
+			static final ContainerDiffFilter INSTANCE = new ContainerDiffFilter();
+
+			@Override
+			public boolean include(final TreeWalk tw)
+					throws MissingObjectException,
+					IncorrectObjectTypeException, IOException {
+
+				if (tw.getFileMode(T_HEAD) == FileMode.MISSING
+						&& tw.getFileMode(T_INDEX) == FileMode.MISSING)
+					return false; // Untracked and unstaged, so not dirty
+
+				if (DEBUG_DECORATIONS)
+					debug("checking '" + tw.getPathString() + "'");
+
+				int mHead = tw.getRawMode(T_HEAD);
+				int mIndex = tw.getRawMode(T_INDEX);
+				int mWorkspace = tw.getRawMode(T_WORKSPACE);
+
+				if (!FS.INSTANCE.supportsExecute()) {
+					// We need to clear the executable bits of the head and
+					// index modes, to prevent false positives when the
+					// resource is +x in the repository but not on disk
+					mHead &= (~0111);
+					mIndex &= (~0111);
+				}
+
+				if (DEBUG_DECORATIONS) {
+					debug("mHead=" + Integer.toOctalString(mHead) + " mIndex="
+							+ Integer.toOctalString(mIndex) + " mWorkspace="
+							+ Integer.toOctalString(mWorkspace), 1);
+				}
+
+				if (mHead != mIndex || mHead != mWorkspace) {
+					// If all three modes aren't identical there is a difference
+					// here. Its the fastest test we have and it neatly handles
+					// weird D->F/F->D style changes that may cause issues later
+
+					if (DEBUG_DECORATIONS) {
+						debug("Modes did not match, so concider the whole "
+								+ "tree dirty", 1);
+					}
+
+					return true;
+				}
+
+				if (FileMode.TREE.equals(mHead)) {
+					// We must include trees otherwise the walker won't recurse
+					// into a subtree for us. Computing anything more about a
+					// tree is too expensive from the workspace so we do not do
+					// a prune based on no changes.
+
+					final IPreferenceStore store = Activator.getDefault()
+							.getPreferenceStore();
+					return store
+							.getBoolean(UIPreferences.DECORATOR_COMPUTE_DEEP_DIRTY);
+				}
+
+				final DirCacheIterator iIndex = tw.getTree(T_INDEX,
+						DirCacheIterator.class);
+				final WorkingTreeIterator iWorkspace = tw.getTree(T_WORKSPACE,
+						WorkingTreeIterator.class);
+
+				if (!timestampMatches(iIndex, iWorkspace)) {
+					// If the modification time of the file differs we know the
+					// folder is (possibly) dirty and should be marked as such.
+					// This is faster than the id check below so it goes first.
+
+					if (DEBUG_DECORATIONS) {
+						debug("Timestamps did not match, so concider the "
+								+ "whole tree dirty", 1);
+					}
+
+					return true;
+				}
+
+				if (!tw.idEqual(T_HEAD, T_INDEX)) {
+					// Staged id difference indicates the path is modified.
+
+					if (DEBUG_DECORATIONS) {
+						debug("File is staged, so concider the whole "
+								+ "tree dirty", 1);
+					}
+
+					return 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
+
+				return false;
+			}
+
+			@Override
+			public boolean shouldBeRecursive() {
+				return true;
+			}
+
+			@Override
+			public TreeFilter clone() {
+				return this;
+			}
+		}
+
 		public DecoratableResourceAdapter(IResource resourceToWrap)
 				throws IOException {
 			resource = resourceToWrap;
@@ -219,25 +364,12 @@ public DecoratableResourceAdapter(IResource resourceToWrap)
 			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;
-			}
-		}
+			// TODO: Add option to shorten branch name to 6 chars if it's a SHA
+			branch = repository.getBranch();
 
-		private void extractFileProperties() throws IOException {
 			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
-			if (treeWalk == null)
-				return;
-
-			if (treeWalk.next())
+			if (resource.getType() == IResource.PROJECT
+					|| (treeWalk != null && treeWalk.next()))
 				tracked = true;
 			else
 				return;
@@ -248,6 +380,27 @@ private void extractFileProperties() throws IOException {
 				return;
 			}
 
+			switch (resource.getType()) {
+			case IResource.FILE:
+				extractFileProperties(treeWalk);
+				break;
+
+			case IResource.FOLDER:
+				extractContainerProperties();
+				break;
+
+			case IResource.PROJECT:
+				final IPreferenceStore store = Activator.getDefault()
+						.getPreferenceStore();
+				if (!store
+						.getBoolean(UIPreferences.DECORATOR_COMPUTE_DEEP_DIRTY))
+					return;
+				extractContainerProperties();
+				break;
+			}
+		}
+
+		private void extractFileProperties(TreeWalk treeWalk) {
 			final DirCacheIterator indexIterator = treeWalk.getTree(T_INDEX,
 					DirCacheIterator.class);
 			final DirCacheEntry indexEntry = indexIterator != null ? indexIterator
@@ -265,18 +418,7 @@ private void extractFileProperties() throws IOException {
 						.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) {
+					if (!timestampMatches(indexIterator, resource)) {
 						// 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
@@ -290,37 +432,27 @@ private void extractFileProperties() throws IOException {
 					}
 				}
 			}
-
 		}
 
 		private void extractContainerProperties() throws IOException {
 			TreeWalk treeWalk = createHeadVsIndexTreeWalk();
-			if (treeWalk == null)
-				return;
-
-			if (treeWalk.next())
-				tracked = true;
+			treeWalk.setFilter(AndTreeFilter.create(treeWalk.getFilter(),
+					ContainerDiffFilter.INSTANCE));
+			treeWalk.setRecursive(true);
+
+			if (repository.getWorkDir().equals(
+					resource.getProject().getLocation().toFile()))
+				treeWalk.addTree(new ContainerTreeIterator(resource
+						.getProject()));
 			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
+				treeWalk.addTree(new ContainerTreeIterator(resource
+						.getWorkspace().getRoot()));
 
+			// TODO: Add fallback for projects with the repository more than
+			// one parent up, for example by using a stack of DummyIterators
+			
+			if (treeWalk.next())
+				dirty = true;
 		}
 
 		/**
@@ -328,9 +460,10 @@ private void extractProjectProperties() throws IOException {
 		 * 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>.
+		 * 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
@@ -344,9 +477,12 @@ private boolean addResourceFilter(final TreeWalk treeWalk,
 				final IResource resourceToFilterBy) {
 			Set<String> repositoryPaths = Collections.singleton(mapping
 					.getRepoRelativePath(resourceToFilterBy));
-			if (repositoryPaths.isEmpty() || repositoryPaths.contains(""))
+			if (repositoryPaths.isEmpty())
 				return false;
 
+			if (repositoryPaths.contains(""))
+				return true; // Project filter
+
 			treeWalk.setFilter(PathFilterGroup
 					.createFromStrings(repositoryPaths));
 			return true;
@@ -376,6 +512,31 @@ private TreeWalk createHeadVsIndexTreeWalk() throws IOException {
 			return treeWalk;
 		}
 
+		private static boolean timestampMatches(DirCacheIterator index,
+				IResource workspaceResource) {
+			return timestampMatches(index.getDirCacheEntry().getLastModified(),
+					workspaceResource.getLocalTimeStamp());
+		}
+
+		private static boolean timestampMatches(DirCacheIterator index,
+				WorkingTreeIterator workspaceResource) {
+			return timestampMatches(index.getDirCacheEntry().getLastModified(),
+					workspaceResource.getEntryLastModified());
+		}
+
+		private static boolean timestampMatches(long index,
+				long workspaceResource) {
+			// 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 (index % 1000 == 0) {
+				return index == (workspaceResource - (workspaceResource % 1000));
+			} else {
+				return index == workspaceResource;
+			}
+		}
+
 		public String getName() {
 			return resource.getName();
 		}
@@ -555,11 +716,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;
 			}
 
@@ -657,6 +820,9 @@ public static void decorate(IDecoration decoration, String format,
 	 * Perform a blanket refresh of all decorations
 	 */
 	public static void refresh() {
+		if (DEBUG_DECORATIONS)
+			debug("blanket refesh");
+
 		Display.getDefault().asyncExec(new Runnable() {
 			public void run() {
 				Activator.getDefault().getWorkbench().getDecoratorManager()
@@ -679,6 +845,9 @@ public void propertyChange(PropertyChangeEvent event) {
 		if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED)
 				|| prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED)
 				|| prop.equals(Activator.DECORATORS_CHANGED)) {
+			if (DEBUG_DECORATIONS)
+				debug("property=" + prop);
+
 			postLabelEvent(new LabelProviderChangedEvent(this));
 		}
 	}
@@ -696,9 +865,20 @@ public void propertyChange(PropertyChangeEvent event) {
 	public void resourceChanged(IResourceChangeEvent event) {
 		final Set<IResource> resourcesToUpdate = new HashSet<IResource>();
 
+		if (DEBUG_DECORATIONS)
+			debug("IResourceChangeEvent");
+
 		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
@@ -709,6 +889,11 @@ public boolean visit(IResourceDelta delta) throws CoreException {
 						// Ignore the change
 						return true;
 					}
+
+					if (DEBUG_DECORATIONS)
+						debug("Git resource: "
+								+ resource.getLocation().toString(), 1);
+
 					if (resource.getType() == IResource.ROOT) {
 						// Continue with the delta
 						return true;
@@ -720,16 +905,10 @@ 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);
+					if (DEBUG_DECORATIONS)
+						debug("Will update this resource", 2);
 					return true;
 				}
 			}, true /* includePhantoms */);
@@ -737,19 +916,25 @@ public boolean visit(IResourceDelta delta) throws CoreException {
 			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)) {
+			if (DEBUG_DECORATIONS)
+				debug("Calulcating dirty ancestor..", 1);
+
 			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);
+					if (resourcesToUpdate.add(current) && DEBUG_DECORATIONS)
+						debug("Will also update " + current.getLocation(), 2);
 				}
 			}
 		}
@@ -768,6 +953,9 @@ postLabelEvent(new LabelProviderChangedEvent(this, resourcesToUpdate
 	 *            The original change event
 	 */
 	private void repositoryChanged(RepositoryChangedEvent e) {
+		if (DEBUG_DECORATIONS)
+			debug("RepositoryChangedEvent=" + e.getRepository().getWorkDir());
+
 		final Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
 		for (final IProject p : ResourcesPlugin.getWorkspace().getRoot()
 				.getProjects()) {
@@ -809,6 +997,9 @@ public void refsChanged(RefsChangedEvent e) {
 	 * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping)
 	 */
 	public void repositoryChanged(RepositoryMapping mapping) {
+		if (DEBUG_DECORATIONS)
+			debug("RepositoryMapping=" + mapping.getWorkDir());
+
 		// 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));
@@ -845,6 +1036,9 @@ private static IResource getResource(Object element) {
 	 *            The event to post
 	 */
 	private void postLabelEvent(final LabelProviderChangedEvent event) {
+		if (DEBUG_DECORATIONS)
+			debug("event=" + event.getElements());
+
 		Display.getDefault().asyncExec(new Runnable() {
 			public void run() {
 				fireLabelProviderChanged(event);
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 74bc209..1a13a15 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
@@ -89,7 +89,9 @@
 
 	private Text projectTextFormat;
 
-	private Button computeDeepDirtyState;
+	private Button recomputeAncestorDecorations;
+
+	private Button computeDeepDirtyStateForContainers;
 
 	private Button showTracked;
 
@@ -204,8 +206,10 @@ private Control createGeneralDecoratorPage(Composite parent) {
 		data.horizontalAlignment = GridData.FILL;
 		composite.setLayoutData(data);
 
-		computeDeepDirtyState = SWTUtils.createCheckBox(composite,
-				UIText.DecoratorPreferencesPage_computeDeep);
+		recomputeAncestorDecorations = SWTUtils.createCheckBox(composite,
+				UIText.DecoratorPreferencesPage_recomputeAncestorDecorations);
+		computeDeepDirtyStateForContainers = SWTUtils.createCheckBox(composite,
+				UIText.DecoratorPreferencesPage_computeDeepDirtyState);
 
 		return composite;
 	}
@@ -317,8 +321,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));
+		computeDeepDirtyStateForContainers.setSelection(store
+				.getBoolean(UIPreferences.DECORATOR_COMPUTE_DEEP_DIRTY));
 
 		fileTextFormat.setText(store
 				.getString(UIPreferences.DECORATOR_FILETEXT_DECORATION));
@@ -342,7 +348,9 @@ public void widgetSelected(SelectionEvent e) {
 			}
 		};
 
-		computeDeepDirtyState.addSelectionListener(selectionListener);
+		recomputeAncestorDecorations.addSelectionListener(selectionListener);
+		computeDeepDirtyStateForContainers
+				.addSelectionListener(selectionListener);
 		showTracked.addSelectionListener(selectionListener);
 		showUntracked.addSelectionListener(selectionListener);
 		showStaged.addSelectionListener(selectionListener);
@@ -384,8 +392,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_COMPUTE_DEEP_DIRTY,
+				computeDeepDirtyStateForContainers.getSelection());
 
 		store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION,
 				fileTextFormat.getText());
@@ -414,8 +424,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));
+		computeDeepDirtyStateForContainers.setSelection(store
+				.getDefaultBoolean(UIPreferences.DECORATOR_COMPUTE_DEEP_DIRTY));
 
 		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 2762d11..770de4a 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
@@ -344,7 +344,8 @@ 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=Also re-decorate &ancestors when decorating changed resources
+DecoratorPreferencesPage_computeDeepDirtyState=Inspect dirty state of &children when decorating folders and projects
 DecoratorPreferencesPage_description=Shows Git specific information on resources in projects under version control.
 
 DecoratorPreferencesPage_decorationSettings=Decoration &settings:
-- 
1.6.1.2.309.g2ea3

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

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05  1:00 [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
  2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
@ 2009-02-05  1:04 ` Tor Arne Vestbø
  2009-02-05 16:06 ` Shawn O. Pearce
  2 siblings, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05  1:04 UTC (permalink / raw
  To: Shawn O. Pearce, Robin Rosenberg; +Cc: git

Tor Arne Vestbø wrote:
> This series adds support for customizable label decorations, which
> is usefull for hiding selected decorations, or tweaking the format
> of the decoration text.

Oh, I forgot, the branch can be found here:

http://repo.or.cz/w/egit/torarne.git?a=shortlog;h=refs/heads/customizable-decorations

Tor Arne

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

* Re: [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners
  2009-02-05  1:00   ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
  2009-02-05  1:00     ` [EGIT PATCH 03/11] Add a specialized team exception for Git Tor Arne Vestbø
@ 2009-02-05 15:48     ` Shawn O. Pearce
  2009-02-05 16:36       ` Tor Arne Vestbø
  1 sibling, 1 reply; 31+ messages in thread
From: Shawn O. Pearce @ 2009-02-05 15:48 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Robin Rosenberg, git

Tor Arne Vestbø <torarnv@gmail.com> wrote:
> +	public static synchronized void removeRepositoryChangeListener(
> +			final RepositoryChangeListener objectThatCares) {
> +		repositoryChangeListeners.remove(objectThatCares);
>  	}
>  
>  	/**
> @@ -131,13 +133,8 @@ 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);
> -	}
> -
> -	private static synchronized RepositoryChangeListener[] getRepositoryChangeListeners() {
> -		return repositoryChangeListeners;
> +		for (RepositoryChangeListener listener : repositoryChangeListeners)
> +			listener.repositoryChanged(which);

See anything wrong here, like that the Set can be modified while
GitProjectData's class lock is held, but its being read here without
any locking?

The array trick worked before because we always made a copy anytime
the array was modified.  So we could safely return the array to the
caller and let the caller iterate it unlocked; we just had to read
the current array using a synchronized method to ensure we had a
stable read.

You'll need to copy the Set somehow while inside of a synchronized
method, then return the copy to the fireRepositoryChanged() method
so it can iterate the copy to fire the events.

-- 
Shawn.

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

* Re: [EGIT PATCH 01/11] Add support code to handle plugin property changes
  2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
  2009-02-05  1:00   ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
@ 2009-02-05 15:53   ` Shawn O. Pearce
  2009-02-05 16:35     ` Tor Arne Vestbø
  1 sibling, 1 reply; 31+ messages in thread
From: Shawn O. Pearce @ 2009-02-05 15:53 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Robin Rosenberg, git

Tor Arne Vestbø <torarnv@gmail.com> wrote:
> 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..d4a9e8e 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
> @@ -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;

What does this field do?  I don't see it referenced anywhere...
  
>  	/**
> +	 * Property listeners for plugin specific events
> +	 */
> +	private static List<IPropertyChangeListener> propertyChangeListeners =
> +		new ArrayList<IPropertyChangeListener>(5);

None of these list accesses are thread-safe.  Are we certain they
call will come from a single thread, e.g. the SWT event thread?
Or do we need to put synchronized protection in here?

-- 
Shawn.

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

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05  1:00 [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
  2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
  2009-02-05  1:04 ` [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
@ 2009-02-05 16:06 ` Shawn O. Pearce
  2009-02-05 16:17   ` Tor Arne Vestbø
  2009-02-05 18:32   ` Robin Rosenberg
  2 siblings, 2 replies; 31+ messages in thread
From: Shawn O. Pearce @ 2009-02-05 16:06 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Robin Rosenberg, git

Tor Arne Vestbø <torarnv@gmail.com> wrote:
> This series adds support for customizable label decorations, which
> is usefull for hiding selected decorations, or tweaking the format
> of the decoration text.

Aside from my two remarks about the synchronized collections,
I like it.  There's no display for "added and dirty", aka doing:

  echo a >a; # create a
  git add a
  echo b >>a; # append a and dirty it

This still shows as just "added".  It should be "added and dirty",
as its partially staged.
 
> Known issues are:
> 
>   - If a project has a repository more than one level above the
>     project directory decorations will fail.

I'd like to see this fixed in the near-ish future, but I don't
think its blocking to merging your patches.
 
>   - When a Java resource is dirty, each parent package in the
>     package hierarcy will appear dirty, even when the layout is
>     set to 'flat'.

Bah.  I've seen the Java compiler error marks also report like this.
I don't think its our issue.  But maybe I'm wrong.

-- 
Shawn.

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

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05 16:06 ` Shawn O. Pearce
@ 2009-02-05 16:17   ` Tor Arne Vestbø
  2009-02-05 18:32   ` Robin Rosenberg
  1 sibling, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 16:17 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Robin Rosenberg, git

Shawn O. Pearce wrote:
> Tor Arne Vestbø <torarnv@gmail.com> wrote:
>> This series adds support for customizable label decorations, which
>> is usefull for hiding selected decorations, or tweaking the format
>> of the decoration text.
> 
> Aside from my two remarks about the synchronized collections,
> I like it. 

Thanks :)

> There's no display for "added and dirty", aka doing:

That's a bug, should work the same as "staged and dirty", with [*] >,
I'll look into it.

>> Known issues are:
>>
>>   - If a project has a repository more than one level above the
>>     project directory decorations will fail.
> 
> I'd like to see this fixed in the near-ish future, but I don't
> think its blocking to merging your patches.

Yepp, It's on my list. This series is basically the minimum feature-set
I could produce that has most of what the current decorators have, so I
 could get a basis landed.

>>   - When a Java resource is dirty, each parent package in the
>>     package hierarcy will appear dirty, even when the layout is
>>     set to 'flat'.
> 
> Bah.  I've seen the Java compiler error marks also report like this.
> I don't think its our issue.  But maybe I'm wrong.

The CVS plugin handles this -- possibly through the TeamStateProvider
and friends -- so it is possible. Something to look at.

Tor Arne

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

* Re: [EGIT PATCH 01/11] Add support code to handle plugin property changes
  2009-02-05 15:53   ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Shawn O. Pearce
@ 2009-02-05 16:35     ` Tor Arne Vestbø
  2009-02-05 16:40       ` Shawn O. Pearce
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 16:35 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Robin Rosenberg, git

Shawn O. Pearce wrote:
> Tor Arne Vestbø <torarnv@gmail.com> wrote:
>> 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..d4a9e8e 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
>> @@ -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;
> 
> What does this field do?  I don't see it referenced anywhere...

It's the singleton instance, referenced from Activator.getDefault(). I
just documented it :)

>>  	/**
>> +	 * Property listeners for plugin specific events
>> +	 */
>> +	private static List<IPropertyChangeListener> propertyChangeListeners =
>> +		new ArrayList<IPropertyChangeListener>(5);
> 
> None of these list accesses are thread-safe.  Are we certain they
> call will come from a single thread, e.g. the SWT event thread?
> Or do we need to put synchronized protection in here?

The addPropertyChangeListener method is called at startup from the
GitLightweightDecorator constructor, in one of the worker threads, and
same thing with removePropertyChangeListener from dispose(). The
broadcastPropertyChange method is called in the main thread every time
the Git decorator preference page is closes.

This is the sync model used by other Eclipse plugins for keeping track
of propertyChangeListeners, for example in the TeamUIPlugin, that's why
I assumed it was OK. I'm perfectly fine with syncrhronizing it though,
similar to repositoryChangeListeners in GitProjectData?

Tor Arne

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

* Re: [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners
  2009-02-05 15:48     ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Shawn O. Pearce
@ 2009-02-05 16:36       ` Tor Arne Vestbø
  2009-02-05 18:28         ` [EGIT PATCH 02/11 v2] " Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 16:36 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Robin Rosenberg, git

Shawn O. Pearce wrote:
> Tor Arne Vestbø <torarnv@gmail.com> wrote:
>> +	public static synchronized void removeRepositoryChangeListener(
>> +			final RepositoryChangeListener objectThatCares) {
>> +		repositoryChangeListeners.remove(objectThatCares);
>>  	}
>>  
>>  	/**
>> @@ -131,13 +133,8 @@ 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);
>> -	}
>> -
>> -	private static synchronized RepositoryChangeListener[] getRepositoryChangeListeners() {
>> -		return repositoryChangeListeners;
>> +		for (RepositoryChangeListener listener : repositoryChangeListeners)
>> +			listener.repositoryChanged(which);
> 
> See anything wrong here, like that the Set can be modified while
> GitProjectData's class lock is held, but its being read here without
> any locking?

You're perfectly right. New patch coming up!

Tor Arne

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

* Re: [EGIT PATCH 01/11] Add support code to handle plugin property changes
  2009-02-05 16:35     ` Tor Arne Vestbø
@ 2009-02-05 16:40       ` Shawn O. Pearce
  2009-02-05 18:22         ` [EGIT PATCH v2] " Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Shawn O. Pearce @ 2009-02-05 16:40 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Robin Rosenberg, git

Tor Arne Vestbø <torarnv@gmail.com> wrote:
> Shawn O. Pearce wrote:
> > Tor Arne Vestbø <torarnv@gmail.com> wrote:
> >> 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..d4a9e8e 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
> >> @@ -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;
> > 
> > What does this field do?  I don't see it referenced anywhere...
> 
> It's the singleton instance, referenced from Activator.getDefault(). I
> just documented it :)

*sigh*.  I haven't had enough caffiene yet this morning.  Yes,
I see what you mean, its only the Javadoc that was added.  OK,
forget I said anything here.  :-)
 
> >>  	/**
> >> +	 * Property listeners for plugin specific events
> >> +	 */
> >> +	private static List<IPropertyChangeListener> propertyChangeListeners =
> >> +		new ArrayList<IPropertyChangeListener>(5);
> > 
> > None of these list accesses are thread-safe.  Are we certain they
> > call will come from a single thread, e.g. the SWT event thread?
> > Or do we need to put synchronized protection in here?
> 
> The addPropertyChangeListener method is called at startup from the
> GitLightweightDecorator constructor, in one of the worker threads, and
> same thing with removePropertyChangeListener from dispose(). The
> broadcastPropertyChange method is called in the main thread every time
> the Git decorator preference page is closes.
> 
> This is the sync model used by other Eclipse plugins for keeping track
> of propertyChangeListeners, for example in the TeamUIPlugin, that's why
> I assumed it was OK. I'm perfectly fine with syncrhronizing it though,
> similar to repositoryChangeListeners in GitProjectData?

OK, I see.  I'd perhaps prefer to make this thread-safe just in case.
If its always coming off the SWT event thread, then just tossing
a synchronized keyword on all 3 methods should be Good Enough(tm).

-- 
Shawn.

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

* [EGIT PATCH v2] Add support code to handle plugin property changes
  2009-02-05 16:40       ` Shawn O. Pearce
@ 2009-02-05 18:22         ` Tor Arne Vestbø
  0 siblings, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 18:22 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] 31+ messages in thread

* [EGIT PATCH 02/11 v2] Use Set instead of array to keep track of change listeners
  2009-02-05 16:36       ` Tor Arne Vestbø
@ 2009-02-05 18:28         ` Tor Arne Vestbø
  0 siblings, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 18:28 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] 31+ messages in thread

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05 16:06 ` Shawn O. Pearce
  2009-02-05 16:17   ` Tor Arne Vestbø
@ 2009-02-05 18:32   ` Robin Rosenberg
  2009-02-05 18:37     ` Tor Arne Vestbø
  1 sibling, 1 reply; 31+ messages in thread
From: Robin Rosenberg @ 2009-02-05 18:32 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Tor Arne Vestbø, git

torsdag 05 februari 2009 17:06:25 skrev Shawn O. Pearce:
> > Known issues are:
> > 
> >   - If a project has a repository more than one level above the
> >     project directory decorations will fail.
> 
> I'd like to see this fixed in the near-ish future, but I don't
> think its blocking to merging your patches.

I'm not sure this is Tore's bug. We've had problems with nested
repos before.

> >   - When a Java resource is dirty, each parent package in the
> >     package hierarcy will appear dirty, even when the layout is
> >     set to 'flat'.
> 
> Bah.  I've seen the Java compiler error marks also report like this.
> I don't think its our issue.  But maybe I'm wrong.

It's our choice here. Generally, I think we should shift behavior here
depending on layout. Java packages are not hierarchical in nature.

Currently it may look like this with flat package layout:

	>org.spearce.jgit.treewalk
		CanonicalTreeParserTest.java
		EmptyTreeIteratorTest.java
		FileTreeIteratorTest.java
		NameConflictTreeWalkTest.java
		PostOrderTreeWalkTest.java
		TreeWalkBasicDiffTest.java

	>org.spearce.jgit.treewalk.filter
		AlwaysCloneTreeFilter.java
		>NotTreeFilterTest.java
		TreeFilterTest.java


Which is odd when looking at org.spearce.jgit.treewalk, because
no files in that package have been changed. A tricky question is
how to decorate empty packages. like org, org.spearce. Probably
as unchanged.

-- robin

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

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05 18:32   ` Robin Rosenberg
@ 2009-02-05 18:37     ` Tor Arne Vestbø
  2009-02-05 22:09       ` Robin Rosenberg
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 18:37 UTC (permalink / raw
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> It's our choice here. Generally, I think we should shift behavior here
> depending on layout. Java packages are not hierarchical in nature.
> 
> Currently it may look like this with flat package layout:
> 
> 	>org.spearce.jgit.treewalk
> 		CanonicalTreeParserTest.java
> 		EmptyTreeIteratorTest.java
> 		FileTreeIteratorTest.java
> 		NameConflictTreeWalkTest.java
> 		PostOrderTreeWalkTest.java
> 		TreeWalkBasicDiffTest.java
> 
> 	>org.spearce.jgit.treewalk.filter
> 		AlwaysCloneTreeFilter.java
> 		>NotTreeFilterTest.java
> 		TreeFilterTest.java
> 
> 
> Which is odd when looking at org.spearce.jgit.treewalk, because
> no files in that package have been changed.

Agreed. But, as the CVS plugin seems to handle this gracefully, without
any JDT specific code (from my brief investigations), it seems that this
is something we can get for free from the team plugins. I will look into
it as soon as this series is done cooking.

Tor Arne

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05  1:00           ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Tor Arne Vestbø
  2009-02-05  1:00             ` [EGIT PATCH 07/11] Add binding for name of the current branch Tor Arne Vestbø
@ 2009-02-05 20:02             ` Robin Rosenberg
  2009-02-05 20:21               ` Tor Arne Vestbø
  2009-02-05 20:04             ` Robin Rosenberg
  2 siblings, 1 reply; 31+ messages in thread
From: Robin Rosenberg @ 2009-02-05 20:02 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git

[-- Attachment #1: Type: text/plain, Size: 973 bytes --]

torsdag 05 februari 2009 02:00:13 skrev Tor Arne Vestbø:
> Currently the only binding available is the resource name, but
> this commit enables a framework for adding more bindings.

The size of the components of the page is wrong forcing unnecessary scrollbars.

The terms use should probably have a cleared explanation/definition 
and the same terms used for the flags/icons. The explanation could
probably be done using longer tooltips.

I miss the red attention-gathering red conflict icon we had. I also
have a hard time distinguishing the staged/added/removed icons
without reading glasses. The decorations are only 1.3 mm on my
screen (150 dpi), so I think the icons should have different colors and
different shapes instead of a few different pixels withing a square 
box.

I'd also like to see the conflict state for files inherited by parent
folder and projects.

The checkboxes in the general tab have no effect on the preview.

-- robin

[-- Attachment #2: prefpage --]
[-- Type: image/png, Size: 23298 bytes --]

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05  1:00           ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Tor Arne Vestbø
  2009-02-05  1:00             ` [EGIT PATCH 07/11] Add binding for name of the current branch Tor Arne Vestbø
  2009-02-05 20:02             ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Robin Rosenberg
@ 2009-02-05 20:04             ` Robin Rosenberg
  2 siblings, 0 replies; 31+ messages in thread
From: Robin Rosenberg @ 2009-02-05 20:04 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git


The "assume unchanged" decoration seems lost.

-- robin

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05 20:02             ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Robin Rosenberg
@ 2009-02-05 20:21               ` Tor Arne Vestbø
  2009-02-05 21:00                 ` Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 20:21 UTC (permalink / raw
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> torsdag 05 februari 2009 02:00:13 skrev Tor Arne Vestbø:
>> Currently the only binding available is the resource name, but
>> this commit enables a framework for adding more bindings.
> 
> The size of the components of the page is wrong forcing unnecessary scrollbars.

Argh, I've been battling those layouts on Linux, but though it was
finally fixed. On Windows the dialog looks fine. I'll investigate further.

> The terms use should probably have a cleared explanation/definition 
> and the same terms used for the flags/icons. The explanation could
> probably be done using longer tooltips.

Noted.

> I miss the red attention-gathering red conflict icon we had.

Happy to replace. If I remember correctly that's the symbol used by
other plugins to indicate conflicts, so I was aiming for consistency and
recognizability.

> I also have a hard time distinguishing the staged/added/removed icons
> without reading glasses. The decorations are only 1.3 mm on my 
> screen (150 dpi), so I think the icons should have different colors
> and different shapes instead of a few different pixels withing a
> square box.>

Noted, I'll try to make some mocukups of variations to the icons.

> I'd also like to see the conflict state for files inherited by parent
> folder and projects.

Good idea, like the synchronize view shows it. Noted.

> The checkboxes in the general tab have no effect on the preview.

True. Should be easy to fix.

Thanks for the feedback!

Tor Arne

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05 20:21               ` Tor Arne Vestbø
@ 2009-02-05 21:00                 ` Tor Arne Vestbø
  2009-02-05 21:36                   ` Robin Rosenberg
  0 siblings, 1 reply; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 21:00 UTC (permalink / raw
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Tor Arne Vestbø wrote:
>> I miss the red attention-gathering red conflict icon we had.
> 
> Happy to replace. If I remember correctly that's the symbol used by
> other plugins to indicate conflicts, so I was aiming for consistency and
> recognizability.

Okey, I've replaced the blue one with the default conflict icon from the
team org.eclipse.team.ui plugin, what can be more consistent then that?
:) See mockup below.

>> I also have a hard time distinguishing the staged/added/removed icons
>> without reading glasses. The decorations are only 1.3 mm on my 
>> screen (150 dpi), so I think the icons should have different colors
>> and different shapes instead of a few different pixels withing a
>> square box.>
> 
> Noted, I'll try to make some mocukups of variations to the icons.

Here are two alternatives. The first one (A) uses the same icons as
before, but adds red and green shading. The second one (B) uses the
green plus sign for added (which is used all over the place in Eclipse
to mean 'added'), and the gray x for removed (which is also used all
over the place in Eclipse for that same concept).

http://img443.imageshack.us/img443/5138/egitmockupmm7.png

I favor solution B, as it uses recognizable concepts from the existing
Eclipse UI, plus that having a red removed-icon conflicts too much with
the conflict-icon (pun intended). There's also a very similar red icon
in JDT that indicates that something needs fixing (a red medic kit),
which is another reason to go with solution B.

What do you say?

Tor Arne

> 
>> I'd also like to see the conflict state for files inherited by parent
>> folder and projects.
> 
> Good idea, like the synchronize view shows it. Noted.
> 
>> The checkboxes in the general tab have no effect on the preview.
> 
> True. Should be easy to fix.
> 
> Thanks for the feedback!
> 
> Tor Arne

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05 21:00                 ` Tor Arne Vestbø
@ 2009-02-05 21:36                   ` Robin Rosenberg
  2009-02-05 21:44                     ` Tor Arne Vestbø
  0 siblings, 1 reply; 31+ messages in thread
From: Robin Rosenberg @ 2009-02-05 21:36 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git

torsdag 05 februari 2009 22:00:36 skrev Tor Arne Vestbø:
> Here are two alternatives. The first one (A) uses the same icons as
> before, but adds red and green shading. The second one (B) uses the
> green plus sign for added (which is used all over the place in Eclipse
> to mean 'added'), and the gray x for removed (which is also used all
> over the place in Eclipse for that same concept).
> 
> http://img443.imageshack.us/img443/5138/egitmockupmm7.png
> 
> I favor solution B, as it uses recognizable concepts from the existing
> Eclipse UI, plus that having a red removed-icon conflicts too much with
> the conflict-icon (pun intended). There's also a very similar red icon
> in JDT that indicates that something needs fixing (a red medic kit),
> which is another reason to go with solution B.

I go with B.

Btw, shouldn't staged, added and removed also count as outgoing, i.e. ">" ?

-- robin

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

* Re: [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences
  2009-02-05 21:36                   ` Robin Rosenberg
@ 2009-02-05 21:44                     ` Tor Arne Vestbø
  0 siblings, 0 replies; 31+ messages in thread
From: Tor Arne Vestbø @ 2009-02-05 21:44 UTC (permalink / raw
  To: Robin Rosenberg; +Cc: Shawn O. Pearce, git

Robin Rosenberg wrote:
> torsdag 05 februari 2009 22:00:36 skrev Tor Arne Vestbø:
>> Here are two alternatives. The first one (A) uses the same icons as
>> before, but adds red and green shading. The second one (B) uses the
>> green plus sign for added (which is used all over the place in Eclipse
>> to mean 'added'), and the gray x for removed (which is also used all
>> over the place in Eclipse for that same concept).
>>
>> http://img443.imageshack.us/img443/5138/egitmockupmm7.png
>>
>> I favor solution B, as it uses recognizable concepts from the existing
>> Eclipse UI, plus that having a red removed-icon conflicts too much with
>> the conflict-icon (pun intended). There's also a very similar red icon
>> in JDT that indicates that something needs fixing (a red medic kit),
>> which is another reason to go with solution B.
> 
> I go with B.

All right :)

> Btw, shouldn't staged, added and removed also count as outgoing, i.e. ">" ?

Currently there's two variables, 'dirty', and 'staged'. The former
mappps to a diff between working directory and index, and has the
default symbol '>'. The latter mapps to a diff between index and
repository, with the default symbol '*'.

The default decoration for files does not have the 'staged' variable,
but you can add {staged} for a *, or override with {staged:>} for a >.

The idea was to get both states between wd and index and index and repo
in the decorators. We could add an option to treat both as one, ie only
care about wd vs repo?

I plan to implement the synchronize view too, in which case we could
share these two pieces of information between the Package Explorer (wd
vs index), and the Synchronize View (index vs repo), similar to how git
gui works.

Tor Arne

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

* Re: [EGIT PATCH 00/11] Support customizable label decorations
  2009-02-05 18:37     ` Tor Arne Vestbø
@ 2009-02-05 22:09       ` Robin Rosenberg
  0 siblings, 0 replies; 31+ messages in thread
From: Robin Rosenberg @ 2009-02-05 22:09 UTC (permalink / raw
  To: Tor Arne Vestbø; +Cc: Shawn O. Pearce, git

torsdag 05 februari 2009 19:37:32 skrev Tor Arne Vestbø:
> Robin Rosenberg wrote:
> > It's our choice here. Generally, I think we should shift behavior here
> > depending on layout. Java packages are not hierarchical in nature.
> > 
> > Currently it may look like this with flat package layout:
> > 
> > 	>org.spearce.jgit.treewalk
> > 		CanonicalTreeParserTest.java
> > 		EmptyTreeIteratorTest.java
> > 		FileTreeIteratorTest.java
> > 		NameConflictTreeWalkTest.java
> > 		PostOrderTreeWalkTest.java
> > 		TreeWalkBasicDiffTest.java
> > 
> > 	>org.spearce.jgit.treewalk.filter
> > 		AlwaysCloneTreeFilter.java
> > 		>NotTreeFilterTest.java
> > 		TreeFilterTest.java
> > 
> > 
> > Which is odd when looking at org.spearce.jgit.treewalk, because
> > no files in that package have been changed.
> 
> Agreed. But, as the CVS plugin seems to handle this gracefully, without
> any JDT specific code (from my brief investigations), it seems that this
> is something we can get for free from the team plugins. I will look into
> it as soon as this series is done cooking.

My guess is it has to do with traversal according to the structure layout, so
JDT only does the layout and the CVS decorator follows that one.

-- robin

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

end of thread, other threads:[~2009-02-05 22:10 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-02-05  1:00 [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
2009-02-05  1:00 ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Tor Arne Vestbø
2009-02-05  1:00   ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Tor Arne Vestbø
2009-02-05  1:00     ` [EGIT PATCH 03/11] Add a specialized team exception for Git Tor Arne Vestbø
2009-02-05  1:00       ` [EGIT PATCH 04/11] Add new class ExceptionCollector for grouping exceptions Tor Arne Vestbø
2009-02-05  1:00         ` [EGIT PATCH 05/11] Add new class SWTUtils with helper-methods for creating controls Tor Arne Vestbø
2009-02-05  1:00           ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Tor Arne Vestbø
2009-02-05  1:00             ` [EGIT PATCH 07/11] Add binding for name of the current branch Tor Arne Vestbø
2009-02-05  1:00               ` [EGIT PATCH 08/11] Add icon decoration for tracked and untracked resources Tor Arne Vestbø
2009-02-05  1:00                 ` [EGIT PATCH 09/11] Implement decorations of dirty, staged, and conflicting resources Tor Arne Vestbø
2009-02-05  1:00                   ` [EGIT PATCH 10/11] Don't decorate every single resource on repository change Tor Arne Vestbø
2009-02-05  1:00                     ` [EGIT PATCH 11/11] Implement label decorations for folders and projects Tor Arne Vestbø
2009-02-05 20:02             ` [EGIT PATCH 06/11] Implement basic customizable label decorations with preferences Robin Rosenberg
2009-02-05 20:21               ` Tor Arne Vestbø
2009-02-05 21:00                 ` Tor Arne Vestbø
2009-02-05 21:36                   ` Robin Rosenberg
2009-02-05 21:44                     ` Tor Arne Vestbø
2009-02-05 20:04             ` Robin Rosenberg
2009-02-05 15:48     ` [EGIT PATCH 02/11] Use Set instead of array to keep track of change listeners Shawn O. Pearce
2009-02-05 16:36       ` Tor Arne Vestbø
2009-02-05 18:28         ` [EGIT PATCH 02/11 v2] " Tor Arne Vestbø
2009-02-05 15:53   ` [EGIT PATCH 01/11] Add support code to handle plugin property changes Shawn O. Pearce
2009-02-05 16:35     ` Tor Arne Vestbø
2009-02-05 16:40       ` Shawn O. Pearce
2009-02-05 18:22         ` [EGIT PATCH v2] " Tor Arne Vestbø
2009-02-05  1:04 ` [EGIT PATCH 00/11] Support customizable label decorations Tor Arne Vestbø
2009-02-05 16:06 ` Shawn O. Pearce
2009-02-05 16:17   ` Tor Arne Vestbø
2009-02-05 18:32   ` Robin Rosenberg
2009-02-05 18:37     ` Tor Arne Vestbø
2009-02-05 22:09       ` Robin Rosenberg

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).