# [EGIT PATCH 0/9] Repository change listeners

19 messages from 2008-07-10 to 2008-07-11. Participants: Robin Rosenberg, Shawn O. Pearce, Andreas Ericsson.
Thread: https://gitlist.dev/t/14400

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 0/9] Repository change listeners
Message-ID: <1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-1-git-send-email-robin.rosenberg%40dewire.com

```
We want to make the UI react on changes to the repo, but the parts
of the code that makes the changes doesn't know who wants to react
on them. This adds a publish-subcribe mechanism, including detection
of externally made changes, e.g. by C Git.

-- robin

Robin Rosenberg (9):
  Create a listener structure for changes to refs and index
  Cached modification times for symbolic refs too
  Connect the history page to the refs update subscription mechanism
  Add a method to listen to changes in any repository
  Add a job to periodically scan for repository changes
  Change GitHistoryPage to listen on any repository.
  Add a job to refresh projects when the index changes.
  Make git dectected changes depend on the automatic refresh setting
  Attach the resource decorator to the repository change event
    mechanism

 .../src/org/spearce/egit/ui/Activator.java         |  155 ++++++++++++++++++++
 .../internal/decorators/GitResourceDecorator.java  |   29 ++++-
 .../egit/ui/internal/history/GitHistoryPage.java   |   43 +++++-
 .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +
 .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++
 .../src/org/spearce/jgit/lib/RefDatabase.java      |   63 ++++++---
 .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++
 .../src/org/spearce/jgit/lib/Repository.java       |   74 ++++++++++
 .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 +++++++
 .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++
 .../org/spearce/jgit/lib/RepositoryListener.java   |   63 ++++++++
 11 files changed, 635 insertions(+), 23 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 1/9] Create a listener structure for changes to refs and index
Message-ID: <1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-2-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com>

```
This version does not tell you which refs have changed, nor
what changes have happened. There is not scanning for externally
initiated changes either, though such changes can be found when
a JGit client wants to read index or refs information.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../egit/ui/internal/history/GitHistoryPage.java   |   31 +++++++++-
 .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +
 .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++++++++++++
 .../src/org/spearce/jgit/lib/RefDatabase.java      |   17 +++++
 .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |   41 +++++++++++++
 .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 ++++++++++++++++
 .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++++++++++++++
 .../org/spearce/jgit/lib/RepositoryListener.java   |   63 +++++++++++++++++++
 9 files changed, 382 insertions(+), 1 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
index 6b55185..7e2f726 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
@@ -61,7 +61,10 @@ import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
 import org.spearce.jgit.lib.AnyObjectId;
+import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryListener;
 import org.spearce.jgit.revplot.PlotCommit;
 import org.spearce.jgit.revwalk.RevCommit;
 import org.spearce.jgit.revwalk.RevFlag;
@@ -73,7 +76,7 @@ import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 import org.spearce.jgit.treewalk.filter.TreeFilter;
 
 /** Graphical commit history viewer. */
-public class GitHistoryPage extends HistoryPage {
+public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 	private static final String PREF_COMMENT_WRAP = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP;
 
 	private static final String PREF_COMMENT_FILL = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL;
@@ -230,6 +233,32 @@ public class GitHistoryPage extends HistoryPage {
 		layout();
 	}
 
+	private Runnable refschangedRunnable;
+
+	public void refsChanged(final RefsChangedEvent e) {
+		if (getControl().isDisposed())
+			return;
+
+		synchronized (this) {
+			if (refschangedRunnable == null) {
+				refschangedRunnable = new Runnable() {
+					public void run() {
+						if (!getControl().isDisposed()) {
+							Activator.trace("Executing async repository changed event");
+							refschangedRunnable = null;
+							inputSet();
+						}
+					}
+				};
+				getControl().getDisplay().asyncExec(refschangedRunnable);
+			}
+		}
+	}
+
+	public void indexChanged(final IndexChangedEvent e) {
+		// We do not use index information here now
+	}
+
 	private void finishContextMenu() {
 		popupMgr.add(new Separator());
 		popupMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
index 5be404e..c7a4402 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
@@ -146,6 +146,7 @@ public class GitIndex {
 	public void rereadIfNecessary() throws IOException {
 		if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
 			read();
+			db.fireIndexChanged();
 		}
 	}
 
@@ -269,6 +270,8 @@ public class GitIndex {
 						"Could not rename temporary index file to index");
 			changed = false;
 			statDirty = false;
+			lastCacheTime = cacheFile.lastModified();
+			db.fireIndexChanged();
 		} finally {
 			if (!lock.delete())
 				throw new IOException(
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
new file mode 100644
index 0000000..30a40d1
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class IndexChangedEvent extends RepositoryChangedEvent {
+	IndexChangedEvent(final Repository repository) {
+		super(repository);
+	}
+
+	@Override
+	public String toString() {
+		return "IndexChangedEvent[" + getRepository() + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
index 9e3e020..4be33b8 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
@@ -81,6 +81,10 @@ class RefDatabase {
 
 	private long packedRefsLength;
 
+	long lastRefModification;
+
+	long lastNotifiedRefModification;
+
 	RefDatabase(final Repository r) {
 		db = r;
 		gitDir = db.getDirectory();
@@ -132,6 +136,8 @@ class RefDatabase {
 
 	void stored(final String name, final ObjectId id, final long time) {
 		looseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));
+		setModified();
+		db.fireRefsMaybeChanged();
 	}
 
 	/**
@@ -155,6 +161,12 @@ class RefDatabase {
 		}
 		if (!lck.commit())
 			throw new ObjectWritingException("Unable to write " + name);
+		setModified();
+		db.fireRefsMaybeChanged();
+	}
+
+	void setModified() {
+		lastRefModification = System.currentTimeMillis();
 	}
 
 	Ref readRef(final String partialName) throws IOException {
@@ -192,6 +204,7 @@ class RefDatabase {
 		readPackedRefs(avail);
 		readLooseRefs(avail, REFS_SLASH, refsDir);
 		readOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));
+		db.fireRefsMaybeChanged();
 		return avail;
 	}
 
@@ -321,6 +334,8 @@ class RefDatabase {
 			return r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);
 		}
 
+		setModified();
+
 		final ObjectId id;
 		try {
 			id = ObjectId.fromString(line);
@@ -378,6 +393,7 @@ class RefDatabase {
 			packedRefsLastModified = currTime;
 			packedRefsLength = currLen;
 			packedRefs = newPackedRefs;
+			setModified();
 		} catch (FileNotFoundException noPackedRefs) {
 			// Ignore it and leave the new map empty.
 			//
@@ -414,4 +430,5 @@ class RefDatabase {
 			lastModified = mtime;
 		}
 	}
+
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
new file mode 100644
index 0000000..c8936c7
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RefsChangedEvent extends RepositoryChangedEvent {
+	RefsChangedEvent(final Repository repository) {
+		super(repository);
+	}
+
+	@Override
+	public String toString() {
+		return "RefsChangedEvent[" + getRepository() + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 04d9b13..6f78652 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -49,7 +49,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
+import java.util.Vector;
 
 import org.spearce.jgit.errors.IncorrectObjectTypeException;
 import org.spearce.jgit.errors.RevisionSyntaxException;
@@ -92,6 +94,8 @@ public class Repository {
 
 	private GitIndex index;
 
+	private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
+
 	/**
 	 * Construct a representation of a Git repository.
 	 * 
@@ -1028,4 +1032,41 @@ public class Repository {
 	public File getWorkDir() {
 		return getDirectory().getParentFile();
 	}
+
+	/**
+	 * Register a {@link RepositoryListener} which will be notified
+	 * when ref changes are detected.
+	 *
+	 * @param l
+	 */
+	public void addRepositoryChangedListener(final RepositoryListener l) {
+		listeners.add(l);
+	}
+
+	/**
+	 * Remove a registered {@link RepositoryListener}
+	 * @param l
+	 */
+	public void removeRepositoryChangedListener(final RepositoryListener l) {
+		listeners.remove(l);
+	}
+
+	void fireRefsMaybeChanged() {
+		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
+			refs.lastNotifiedRefModification = refs.lastRefModification;
+			final RefsChangedEvent event = new RefsChangedEvent(this);
+			for (final RepositoryListener l :
+				listeners.toArray(new RepositoryListener[listeners.size()])) {
+				l.refsChanged(event);
+			}
+		}
+	}
+
+	void fireIndexChanged() {
+		final IndexChangedEvent event = new IndexChangedEvent(this);
+		for (final RepositoryListener l :
+			listeners.toArray(new RepositoryListener[listeners.size()])) {
+			l.indexChanged(event);
+		}
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
new file mode 100644
index 0000000..d1ff07d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * A default {@link RepositoryListener} that does nothing except invoke an
+ * optional general method for any repository change.
+ */
+public class RepositoryAdapter implements RepositoryListener {
+
+	public void indexChanged(final IndexChangedEvent e) {
+		// Empty
+	}
+
+	public void refsChanged(final RefsChangedEvent e) {
+		// Empty
+	}
+
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
new file mode 100644
index 0000000..b58df87
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about changed refs to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RepositoryChangedEvent {
+	private final Repository repository;
+
+	RepositoryChangedEvent(final Repository repository) {
+		this.repository = repository;
+	}
+
+	/**
+	 * @return the repository that was changed
+	 */
+	public Repository getRepository() {
+		return repository;
+	}
+
+	@Override
+	public String toString() {
+		return "RepositoryChangedEvent[" + repository + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java
new file mode 100644
index 0000000..ceb14ce
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * A RepositoryListener gets notification about changes in refs or repository.
+ * <p>
+ * It currently does <em>not</em> get notification about which items are
+ * changed.
+ */
+public interface RepositoryListener {
+	/**
+	 * Invoked when a ref changes
+	 *
+	 * @param e
+	 *            information about the changes.
+	 */
+	void refsChanged(RefsChangedEvent e);
+
+	/**
+	 * Invoked when the index changes
+	 *
+	 * @param e
+	 *            information about the changes.
+	 */
+	void indexChanged(IndexChangedEvent e);
+
+}
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 2/9] Cached modification times for symbolic refs too
Message-ID: <1215729651-26781-3-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-3-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com>

```
We want to detect changes to symbolic refs like HEAD. When HEAD is
redirected to another branch, that's a change even if if the branch
head itself did not change.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/lib/RefDatabase.java      |   48 +++++++++++---------
 1 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
index 4be33b8..17a74e5 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
@@ -71,7 +71,8 @@ class RefDatabase {
 
 	private final File refsDir;
 
-	private Map<String, CachedRef> looseRefs;
+	private Map<String, Ref> looseRefs;
+	private Map<String, Long> looseRefsMTime;
 
 	private final File packedRefsFile;
 
@@ -94,7 +95,8 @@ class RefDatabase {
 	}
 
 	void clearCache() {
-		looseRefs = new HashMap<String, CachedRef>();
+		looseRefs = new HashMap<String, Ref>();
+		looseRefsMTime = new HashMap<String, Long>();
 		packedRefs = new HashMap<String, Ref>();
 		packedRefsLastModified = 0;
 		packedRefsLength = 0;
@@ -135,7 +137,8 @@ class RefDatabase {
 	}
 
 	void stored(final String name, final ObjectId id, final long time) {
-		looseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));
+		looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, id));
+		looseRefsMTime.put(name, time);
 		setModified();
 		db.fireRefsMaybeChanged();
 	}
@@ -203,7 +206,11 @@ class RefDatabase {
 		final HashMap<String, Ref> avail = new HashMap<String, Ref>();
 		readPackedRefs(avail);
 		readLooseRefs(avail, REFS_SLASH, refsDir);
-		readOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));
+		try {
+			avail.put(Constants.HEAD, readRefBasic(Constants.HEAD, 0));
+		} catch (IOException e) {
+			// ignore here
+		}
 		db.fireRefsMaybeChanged();
 		return avail;
 	}
@@ -231,13 +238,15 @@ class RefDatabase {
 			final String refName, final File ent) {
 		// Unchanged and cached? Don't read it again.
 		//
-		CachedRef ref = looseRefs.get(refName);
+		Ref ref = looseRefs.get(refName);
 		if (ref != null) {
-			if (ref.lastModified == ent.lastModified()) {
+			Long cachedlastModified = looseRefsMTime.get(refName);
+			if (cachedlastModified != null && cachedlastModified == ent.lastModified()) {
 				avail.put(ref.getName(), ref);
 				return;
 			}
 			looseRefs.remove(refName);
+			looseRefsMTime.remove(refName);
 		}
 
 		// Recurse into the directory.
@@ -269,9 +278,9 @@ class RefDatabase {
 					return;
 				}
 
-				ref = new CachedRef(Ref.Storage.LOOSE, refName, id, ent
-						.lastModified());
+				ref = new Ref(Ref.Storage.LOOSE, refName, id);
 				looseRefs.put(ref.getName(), ref);
+				looseRefsMTime.put(ref.getName(), ent.lastModified());
 				avail.put(ref.getName(), ref);
 			} finally {
 				in.close();
@@ -297,13 +306,15 @@ class RefDatabase {
 		// Prefer loose ref to packed ref as the loose
 		// file can be more up-to-date than a packed one.
 		//
-		CachedRef ref = looseRefs.get(name);
+		Ref ref = looseRefs.get(name);
 		final File loose = fileForRef(name);
 		final long mtime = loose.lastModified();
 		if (ref != null) {
-			if (ref.lastModified == mtime)
+			Long cachedlastModified = looseRefsMTime.get(name);
+			if (cachedlastModified != null && cachedlastModified == mtime)
 				return ref;
 			looseRefs.remove(name);
+			looseRefsMTime.remove(name);
 		}
 
 		if (mtime == 0) {
@@ -331,6 +342,10 @@ class RefDatabase {
 
 			final String target = line.substring("ref: ".length());
 			final Ref r = readRefBasic(target, depth + 1);
+			Long cachedMtime = looseRefsMTime.get(name);
+			if (cachedMtime != null && cachedMtime != mtime)
+				setModified();
+			looseRefsMTime.put(name, mtime);
 			return r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);
 		}
 
@@ -343,8 +358,9 @@ class RefDatabase {
 			throw new IOException("Not a ref: " + name + ": " + line);
 		}
 
-		ref = new CachedRef(Ref.Storage.LOOSE, name, id, mtime);
+		ref = new Ref(Ref.Storage.LOOSE, name, id);
 		looseRefs.put(name, ref);
+		looseRefsMTime.put(name, mtime);
 		return ref;
 	}
 
@@ -421,14 +437,4 @@ class RefDatabase {
 				fileLocation), CHAR_ENC));
 	}
 
-	private static class CachedRef extends Ref {
-		final long lastModified;
-
-		CachedRef(final Storage st, final String refName, final ObjectId id,
-				final long mtime) {
-			super(st, refName, id);
-			lastModified = mtime;
-		}
-	}
-
 }
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 3/9] Connect the history page to the refs update subscription mechanism
Message-ID: <1215729651-26781-4-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-4-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-3-git-send-email-robin.rosenberg@dewire.com>

```
Now the history page can get updated automatically without polling.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../egit/ui/internal/history/GitHistoryPage.java   |   11 ++++++++++-
 1 files changed, 10 insertions(+), 1 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
index 7e2f726..418f3b6 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
@@ -168,6 +168,9 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 	/** Last HEAD */
 	private AnyObjectId currentHeadId;
 
+	/** We need to remember the current repository */
+	private Repository db;
+
 	/**
 	 * Highlight flag that can be applied to commits to make them stand out.
 	 * <p>
@@ -536,6 +539,9 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 			revObjectSelectionProvider.setActiveRepository(null);
 		cancelRefreshJob();
 
+		if (db != null)
+			db.removeRepositoryChangedListener(this);
+
 		if (graph == null)
 			return false;
 
@@ -543,7 +549,8 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 		if (in == null || in.length == 0)
 			return false;
 
-		Repository db = null;
+		db = null;
+
 		final ArrayList<String> paths = new ArrayList<String>(in.length);
 		for (final IResource r : in) {
 			final RepositoryMapping map = RepositoryMapping.getMapping(r);
@@ -563,6 +570,8 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 		if (db == null)
 			return false;
 
+		db.addRepositoryChangedListener(this);
+
 		final AnyObjectId headId;
 		try {
 			headId = db.resolve("HEAD");
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 4/9] Add a method to listen to changes in any repository
Message-ID: <1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-5-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-4-git-send-email-robin.rosenberg@dewire.com>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |   31 +++++++++++++++++--
 1 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 6f78652..dfa3045 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -95,6 +95,7 @@ public class Repository {
 	private GitIndex index;
 
 	private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
+	static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
 
 	/**
 	 * Construct a representation of a Git repository.
@@ -1051,12 +1052,32 @@ public class Repository {
 		listeners.remove(l);
 	}
 
+	/**
+	 * Register a global {@link RepositoryListener} which will be notified
+	 * when a ref changes in any repository are detected.
+	 *
+	 * @param l
+	 */
+	public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
+		allListeners.add(l);
+	}
+
+	/**
+	 * Remove a globally registered {@link RepositoryListener}
+	 * @param l
+	 */
+	public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
+		allListeners.remove(l);
+	}
+
 	void fireRefsMaybeChanged() {
 		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
 			refs.lastNotifiedRefModification = refs.lastRefModification;
 			final RefsChangedEvent event = new RefsChangedEvent(this);
-			for (final RepositoryListener l :
-				listeners.toArray(new RepositoryListener[listeners.size()])) {
+			List<RepositoryListener> all = new ArrayList<RepositoryListener>(
+					listeners);
+			all.addAll(allListeners);
+			for (final RepositoryListener l : all) {
 				l.refsChanged(event);
 			}
 		}
@@ -1064,8 +1085,10 @@ public class Repository {
 
 	void fireIndexChanged() {
 		final IndexChangedEvent event = new IndexChangedEvent(this);
-		for (final RepositoryListener l :
-			listeners.toArray(new RepositoryListener[listeners.size()])) {
+		List<RepositoryListener> all = new ArrayList<RepositoryListener>(
+				listeners);
+		all.addAll(allListeners);
+		for (final RepositoryListener l : all) {
 			l.indexChanged(event);
 		}
 	}
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 5/9] Add a job to periodically scan for repository changes
Message-ID: <1215729651-26781-6-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-6-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/egit/ui/Activator.java         |   75 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |   10 +++
 2 files changed, 85 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 8d1b8cd..3e02c44 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,18 +10,27 @@ package org.spearce.egit.ui;
 
 import java.net.Authenticator;
 import java.net.ProxySelector;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.eclipse.core.net.proxy.IProxyService;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jsch.core.IJSchService;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.eclipse.ui.themes.ITheme;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
+import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.transport.SshSessionFactory;
 
 /**
@@ -127,6 +136,7 @@ public class Activator extends AbstractUIPlugin {
 	}
 
 	private boolean traceVerbose;
+	private RCS rcs;
 
 	/**
 	 * Constructor for the egit ui plugin singleton
@@ -140,6 +150,66 @@ public class Activator extends AbstractUIPlugin {
 		traceVerbose = isOptionSet("/trace/verbose");
 		setupSSH(context);
 		setupProxy(context);
+		setupRepoChangeScanner();
+	}
+
+	static class RCS extends Job {
+		RCS() {
+			super("Repository Change Scanner");
+		}
+
+		// FIXME, need to be more intelligent about this to avoid too much work
+		private static final long REPO_SCAN_INTERVAL = 10000L;
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			try {
+				// A repository can contain many projects, only scan once
+				// (a project could in theory be distributed among many
+				// repositories. We discard that as being ugly and stupid for
+				// the moment.
+				IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+				monitor.beginTask("Scanning Git repositories for changes", projects.length);
+				Set<Repository> scanned = new HashSet<Repository>();
+				for (IProject p : projects) {
+					RepositoryMapping mapping = RepositoryMapping.getMapping(p);
+					if (mapping != null) {
+						Repository r = mapping.getRepository();
+						if (!scanned.contains(r)) {
+							if (monitor.isCanceled())
+								break;
+							trace("Scanning " + r + " for changes");
+							scanned.add(r);
+							ISchedulingRule rule = p.getWorkspace().getRuleFactory().modifyRule(p);
+							getJobManager().beginRule(rule, monitor);
+							try {
+								r.scanForRepoChanges();
+							} finally {
+								getJobManager().endRule(rule);
+							}
+						}
+					}
+					monitor.worked(1);
+				}
+				monitor.done();
+				trace("Rescheduling " + getName() + " job");
+				schedule(REPO_SCAN_INTERVAL);
+			} catch (Exception e) {
+				trace("Stopped rescheduling " + getName() + "job");
+				return new Status(
+						IStatus.ERROR,
+						getPluginId(),
+						0,
+						"An error occurred while scanning for changes. Scanning aborted",
+						e);
+			}
+			return Status.OK_STATUS;
+		}
+	}
+
+	private void setupRepoChangeScanner() {
+		rcs = new RCS();
+		rcs.schedule(RCS.REPO_SCAN_INTERVAL);
 	}
 
 	private void setupSSH(final BundleContext context) {
@@ -165,6 +235,11 @@ public class Activator extends AbstractUIPlugin {
 	}
 
 	public void stop(final BundleContext context) throws Exception {
+		trace("Trying to cancel " + rcs.getName() + " job");
+		if (!rcs.cancel()) {
+			rcs.join();
+		}
+		trace("rcs.getName() " + rcs.getName() + " cancelled ok");
 		super.stop(context);
 		plugin = null;
 	}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index dfa3045..9b65154 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -1092,4 +1092,14 @@ public class Repository {
 			l.indexChanged(event);
 		}
 	}
+
+	/**
+	 * Force a scan for changed refs.
+	 *
+	 * @throws IOException
+	 */
+	public void scanForRepoChanges() throws IOException {
+		getAllRefs(); // This will look for changes to refs
+		getIndex(); // This will detect changes in the index
+	}
 }
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 6/9] Change GitHistoryPage to listen on any repository.
Message-ID: <1215729651-26781-7-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-7-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-6-git-send-email-robin.rosenberg@dewire.com>

```
This makes listening simpler.
---
 .../egit/ui/internal/history/GitHistoryPage.java   |   11 ++++++-----
 1 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
index 418f3b6..e3ff8d4 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
@@ -234,11 +234,16 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 		attachContextMenu(commentViewer.getControl());
 		attachContextMenu(fileViewer.getControl());
 		layout();
+
+		Repository.addAnyRepositoryChangedListener(this);
 	}
 
 	private Runnable refschangedRunnable;
 
 	public void refsChanged(final RefsChangedEvent e) {
+		if (e.getRepository() != db)
+			return;
+
 		if (getControl().isDisposed())
 			return;
 
@@ -480,6 +485,7 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 	}
 
 	public void dispose() {
+		Repository.removeAnyRepositoryChangedListener(this);
 		cancelRefreshJob();
 		if (popupMgr != null) {
 			for (final IContributionItem i : popupMgr.getItems()) {
@@ -539,9 +545,6 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 			revObjectSelectionProvider.setActiveRepository(null);
 		cancelRefreshJob();
 
-		if (db != null)
-			db.removeRepositoryChangedListener(this);
-
 		if (graph == null)
 			return false;
 
@@ -570,8 +573,6 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 		if (db == null)
 			return false;
 
-		db.addRepositoryChangedListener(this);
-
 		final AnyObjectId headId;
 		try {
 			headId = db.resolve("HEAD");
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 7/9] Add a job to refresh projects when the index changes.
Message-ID: <1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-8-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-7-git-send-email-robin.rosenberg@dewire.com>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/egit/ui/Activator.java         |   83 +++++++++++++++++++-
 1 files changed, 79 insertions(+), 4 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 3e02c44..39d3bc9 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
@@ -11,16 +11,19 @@ package org.spearce.egit.ui;
 import java.net.Authenticator;
 import java.net.ProxySelector;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 import org.eclipse.core.net.proxy.IProxyService;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jsch.core.IJSchService;
@@ -30,7 +33,10 @@ import org.eclipse.ui.themes.ITheme;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryListener;
 import org.spearce.jgit.transport.SshSessionFactory;
 
 /**
@@ -137,6 +143,7 @@ public class Activator extends AbstractUIPlugin {
 
 	private boolean traceVerbose;
 	private RCS rcs;
+	private RIRefresh refreshJob;
 
 	/**
 	 * Constructor for the egit ui plugin singleton
@@ -151,6 +158,70 @@ public class Activator extends AbstractUIPlugin {
 		setupSSH(context);
 		setupProxy(context);
 		setupRepoChangeScanner();
+		setupRepoIndexRefresh();
+	}
+
+	private void setupRepoIndexRefresh() {
+		refreshJob = new RIRefresh();
+		Repository.addAnyRepositoryChangedListener(refreshJob);
+	}
+
+	static class RIRefresh extends Job implements RepositoryListener {
+
+		RIRefresh() {
+			super("Git index refresh Job");
+		}
+
+		private Set<IProject> projectsToScan = new LinkedHashSet<IProject>();
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+			monitor.beginTask("Refreshing git managed projects", projects.length);
+
+			while (projectsToScan.size() > 0) {
+				IProject p;
+				synchronized (projectsToScan) {
+					if (projectsToScan.size() == 0) {
+					}
+					p = projectsToScan.iterator().next();
+					projectsToScan.remove(p);
+				}
+				ISchedulingRule rule = p.getWorkspace().getRuleFactory().refreshRule(p);
+				try {
+					getJobManager().beginRule(rule, monitor);
+					p.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 1));
+				} catch (CoreException e) {
+					logError("Failed to refresh projects from index changes", e);
+					return new Status(IStatus.ERROR, getPluginId(), e.getMessage());
+				} finally {
+					getJobManager().endRule(rule);
+				}
+			}
+			monitor.done();
+			return Status.OK_STATUS;
+		}
+
+		public void indexChanged(IndexChangedEvent e) {
+			IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+			Set<IProject> toRefresh= new HashSet<IProject>();
+			for (IProject p : projects) {
+				RepositoryMapping mapping = RepositoryMapping.getMapping(p);
+				if (mapping != null && mapping.getRepository() == e.getRepository()) {
+					toRefresh.add(p);
+				}
+			}
+			synchronized (projectsToScan) {
+				projectsToScan.addAll(toRefresh);
+			}
+			if (projectsToScan.size() > 0)
+				schedule();
+		}
+
+		public void refsChanged(RefsChangedEvent e) {
+			// Do not react here
+		}
+
 	}
 
 	static class RCS extends Job {
@@ -236,10 +307,14 @@ public class Activator extends AbstractUIPlugin {
 
 	public void stop(final BundleContext context) throws Exception {
 		trace("Trying to cancel " + rcs.getName() + " job");
-		if (!rcs.cancel()) {
-			rcs.join();
-		}
-		trace("rcs.getName() " + rcs.getName() + " cancelled ok");
+		rcs.cancel();
+		trace("Trying to cancel " + refreshJob.getName() + " job");
+		refreshJob.cancel();
+
+		rcs.join();
+		refreshJob.join();
+
+		trace("Jobs terminated");
 		super.stop(context);
 		plugin = null;
 	}
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 8/9] Make git dectected changes depend on the automatic refresh setting
Message-ID: <1215729651-26781-9-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-9-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com>

```
This is the same setting that scans the workspace regularly for
changes in resources. We scan more often can can trigger sooner because
we can scan fewer files.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/egit/ui/Activator.java         |    5 +++++
 1 files changed, 5 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 39d3bc9..d8928cb 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
@@ -203,6 +203,11 @@ public class Activator extends AbstractUIPlugin {
 		}
 
 		public void indexChanged(IndexChangedEvent e) {
+			// Check the workspace setting "refresh automatically" setting first
+			if (!ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(
+					ResourcesPlugin.PREF_AUTO_REFRESH))
+				return;
+
 			IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
 			Set<IProject> toRefresh= new HashSet<IProject>();
 			for (IProject p : projects) {
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-10 22:40

Subject: [EGIT PATCH 9/9] Attach the resource decorator to the repository change event mechanism
Message-ID: <1215729651-26781-10-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215729651-26781-10-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <1215729651-26781-9-git-send-email-robin.rosenberg@dewire.com>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../internal/decorators/GitResourceDecorator.java  |   29 +++++++++++++++++++-
 1 files changed, 28 insertions(+), 1 deletions(-)

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
index 0308f6a..dcb87b7 100644
--- 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
@@ -10,6 +10,8 @@
 package org.spearce.egit.ui.internal.decorators;
 
 import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
@@ -38,7 +40,11 @@ import org.spearce.egit.ui.Activator;
 import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIText;
 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;
@@ -61,7 +67,7 @@ public class GitResourceDecorator extends LabelProvider implements
 
 	private static final RCL myrcl = new RCL();
 
-	static class RCL implements RepositoryChangeListener, Runnable {
+	static class RCL implements RepositoryChangeListener, RepositoryListener, Runnable {
 		private boolean requested;
 
 		public synchronized void run() {
@@ -70,6 +76,26 @@ public class GitResourceDecorator extends LabelProvider implements
 					GitResourceDecorator.class.getName());
 		}
 
+		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) {
 			try {
 				which.getContainer().accept(new IResourceVisitor() {
@@ -139,6 +165,7 @@ public class GitResourceDecorator extends LabelProvider implements
 	static ResCL myrescl = new ResCL();
 
 	static {
+		Repository.addAnyRepositoryChangedListener(myrcl);
 		GitProjectData.addRepositoryChangeListener(myrcl);
 		ResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl,
 				IResourceChangeEvent.POST_CHANGE);
-- 
1.5.6.2.220.g44701

```

## Shawn O. Pearce, 2008-07-11 04:22

Subject: Re: [EGIT PATCH 1/9] Create a listener structure for changes to refs and index
Message-ID: <20080711042251.GB32633@spearce.org>
URL: https://gitlist.dev/e/20080711042251.GB32633%40spearce.org
In-Reply-To: <1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com>

```
Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> This version does not tell you which refs have changed, nor
> what changes have happened. There is not scanning for externally
> initiated changes either, though such changes can be found when
> a JGit client wants to read index or refs information.

> diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
> index 9e3e020..4be33b8 100644
> --- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
> +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
> @@ -81,6 +81,10 @@ class RefDatabase {
>  
>  	private long packedRefsLength;
>  
> +	long lastRefModification;
> +
> +	long lastNotifiedRefModification;
> +
>  	RefDatabase(final Repository r) {
>  		db = r;
>  		gitDir = db.getDirectory();
> @@ -155,6 +161,12 @@ class RefDatabase {
>  		}
>  		if (!lck.commit())
>  			throw new ObjectWritingException("Unable to write " + name);
> +		setModified();
> +		db.fireRefsMaybeChanged();
> +	}
> +
> +	void setModified() {
> +		lastRefModification = System.currentTimeMillis();
>  	}

We don't ever actually need the time here as a time, do we?
Wouldn't a simple counter work just as well, but ensure we don't
lose notifications that occur more frequently then the resolution
of currentTimeMillis()?

-- 
Shawn.

```

## Shawn O. Pearce, 2008-07-11 04:28

Subject: Re: [EGIT PATCH 4/9] Add a method to listen to changes in any repository
Message-ID: <20080711042822.GC32633@spearce.org>
URL: https://gitlist.dev/e/20080711042822.GC32633%40spearce.org
In-Reply-To: <1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com>

```
Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
> index 6f78652..dfa3045 100644
> --- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
> +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
> @@ -95,6 +95,7 @@ public class Repository {
>  	private GitIndex index;
>  
>  	private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
> +	static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
...
>  	void fireRefsMaybeChanged() {
>  		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
>  			refs.lastNotifiedRefModification = refs.lastRefModification;
>  			final RefsChangedEvent event = new RefsChangedEvent(this);
> -			for (final RepositoryListener l :
> -				listeners.toArray(new RepositoryListener[listeners.size()])) {
> +			List<RepositoryListener> all = new ArrayList<RepositoryListener>(
> +					listeners);
> +			all.addAll(allListeners);
> +			for (final RepositoryListener l : all) {
>  				l.refsChanged(event);

I don't think this pattern is thread-safe like you think it is.
Adding (or removing) an allListener while you are trying
to copy the allListener collection for delivery can cause a
ConcurrentModificationException.

The preimage here is not even correct because toArray locks the
vector and will grow the input array if it was too small, but it
nulls out the later entries if the array was too large.  Thus you
can NPE inside of the fire loop.

The only safe way to do this is to lock the collection while you copy
it into an array or list, then later iterate that to do the delivery.

Both of the fire implemenations in this patch have this issue.  :-|

-- 
Shawn.

```

## Shawn O. Pearce, 2008-07-11 04:33

Subject: Re: [EGIT PATCH 7/9] Add a job to refresh projects when the index changes.
Message-ID: <20080711043317.GD32633@spearce.org>
URL: https://gitlist.dev/e/20080711043317.GD32633%40spearce.org
In-Reply-To: <1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com>

```
Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> +
> +	static class RIRefresh extends Job implements RepositoryListener {
...
> +			while (projectsToScan.size() > 0) {
> +				IProject p;
> +				synchronized (projectsToScan) {
> +					if (projectsToScan.size() == 0) {
> +					}
> +					p = projectsToScan.iterator().next();

This is going to throw some sort of no such element exception when
you call next when projectsToScan is empty.  You are missing a
break inside of the if test that is meant to guard against this.

> +					projectsToScan.remove(p);
> +				}

You have gone to the trouble of allocating the Iterator to obtain
the next item, you should also just use it to do the deletion of
the next item.  Its quicker to let the Iterator do the remove as
it already has the pointer to the proper element position.

-- 
Shawn.

```

## Shawn O. Pearce, 2008-07-11 05:26

Subject: Re: [EGIT PATCH 0/9] Repository change listeners
Message-ID: <20080711052602.GA822@spearce.org>
URL: https://gitlist.dev/e/20080711052602.GA822%40spearce.org
In-Reply-To: <1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com>

```
Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> We want to make the UI react on changes to the repo, but the parts
> of the code that makes the changes doesn't know who wants to react
> on them. This adds a publish-subcribe mechanism, including detection
> of externally made changes, e.g. by C Git.

Aside from the few patches I commented on, I like the overall series
(not just this one, but the others you sent today as well).  Its a
welcome improvement.
 
-- 
Shawn.

```

## Robin Rosenberg, 2008-07-11 09:27

Subject: [PATCH] Create a listener structure for changes to refs and index
Message-ID: <1215768441-599-1-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215768441-599-1-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <20080711042251.GB32633@spearce.org>

```
This version does not tell you which refs have changed, nor
what changes have happened. There is not scanning for externally
initiated changes either, though such changes can be found when
a JGit client wants to read index or refs information.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../egit/ui/internal/history/GitHistoryPage.java   |   31 +++++++++-
 .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +
 .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++++++++++++
 .../src/org/spearce/jgit/lib/RefDatabase.java      |   19 ++++++
 .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |   41 +++++++++++++
 .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 ++++++++++++++++
 .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++++++++++++++
 .../org/spearce/jgit/lib/RepositoryListener.java   |   63 +++++++++++++++++++
 9 files changed, 384 insertions(+), 1 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java

diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
index 6b55185..7e2f726 100644
--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java
@@ -61,7 +61,10 @@ import org.spearce.egit.ui.UIIcons;
 import org.spearce.egit.ui.UIPreferences;
 import org.spearce.egit.ui.UIText;
 import org.spearce.jgit.lib.AnyObjectId;
+import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryListener;
 import org.spearce.jgit.revplot.PlotCommit;
 import org.spearce.jgit.revwalk.RevCommit;
 import org.spearce.jgit.revwalk.RevFlag;
@@ -73,7 +76,7 @@ import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 import org.spearce.jgit.treewalk.filter.TreeFilter;
 
 /** Graphical commit history viewer. */
-public class GitHistoryPage extends HistoryPage {
+public class GitHistoryPage extends HistoryPage implements RepositoryListener {
 	private static final String PREF_COMMENT_WRAP = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP;
 
 	private static final String PREF_COMMENT_FILL = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL;
@@ -230,6 +233,32 @@ public class GitHistoryPage extends HistoryPage {
 		layout();
 	}
 
+	private Runnable refschangedRunnable;
+
+	public void refsChanged(final RefsChangedEvent e) {
+		if (getControl().isDisposed())
+			return;
+
+		synchronized (this) {
+			if (refschangedRunnable == null) {
+				refschangedRunnable = new Runnable() {
+					public void run() {
+						if (!getControl().isDisposed()) {
+							Activator.trace("Executing async repository changed event");
+							refschangedRunnable = null;
+							inputSet();
+						}
+					}
+				};
+				getControl().getDisplay().asyncExec(refschangedRunnable);
+			}
+		}
+	}
+
+	public void indexChanged(final IndexChangedEvent e) {
+		// We do not use index information here now
+	}
+
 	private void finishContextMenu() {
 		popupMgr.add(new Separator());
 		popupMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
index 5be404e..c7a4402 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
@@ -146,6 +146,7 @@ public class GitIndex {
 	public void rereadIfNecessary() throws IOException {
 		if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
 			read();
+			db.fireIndexChanged();
 		}
 	}
 
@@ -269,6 +270,8 @@ public class GitIndex {
 						"Could not rename temporary index file to index");
 			changed = false;
 			statDirty = false;
+			lastCacheTime = cacheFile.lastModified();
+			db.fireIndexChanged();
 		} finally {
 			if (!lock.delete())
 				throw new IOException(
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
new file mode 100644
index 0000000..30a40d1
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class IndexChangedEvent extends RepositoryChangedEvent {
+	IndexChangedEvent(final Repository repository) {
+		super(repository);
+	}
+
+	@Override
+	public String toString() {
+		return "IndexChangedEvent[" + getRepository() + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
index 9e3e020..ebfa7f5 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
@@ -81,6 +81,12 @@ class RefDatabase {
 
 	private long packedRefsLength;
 
+	long lastRefModification;
+
+	long lastNotifiedRefModification;
+
+	static int refModificationCounter;
+
 	RefDatabase(final Repository r) {
 		db = r;
 		gitDir = db.getDirectory();
@@ -132,6 +138,8 @@ class RefDatabase {
 
 	void stored(final String name, final ObjectId id, final long time) {
 		looseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));
+		setModified();
+		db.fireRefsMaybeChanged();
 	}
 
 	/**
@@ -155,6 +163,12 @@ class RefDatabase {
 		}
 		if (!lck.commit())
 			throw new ObjectWritingException("Unable to write " + name);
+		setModified();
+		db.fireRefsMaybeChanged();
+	}
+
+	void setModified() {
+		lastRefModification = refModificationCounter++;
 	}
 
 	Ref readRef(final String partialName) throws IOException {
@@ -192,6 +206,7 @@ class RefDatabase {
 		readPackedRefs(avail);
 		readLooseRefs(avail, REFS_SLASH, refsDir);
 		readOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));
+		db.fireRefsMaybeChanged();
 		return avail;
 	}
 
@@ -321,6 +336,8 @@ class RefDatabase {
 			return r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);
 		}
 
+		setModified();
+
 		final ObjectId id;
 		try {
 			id = ObjectId.fromString(line);
@@ -378,6 +395,7 @@ class RefDatabase {
 			packedRefsLastModified = currTime;
 			packedRefsLength = currLen;
 			packedRefs = newPackedRefs;
+			setModified();
 		} catch (FileNotFoundException noPackedRefs) {
 			// Ignore it and leave the new map empty.
 			//
@@ -414,4 +432,5 @@ class RefDatabase {
 			lastModified = mtime;
 		}
 	}
+
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
new file mode 100644
index 0000000..c8936c7
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RefsChangedEvent extends RepositoryChangedEvent {
+	RefsChangedEvent(final Repository repository) {
+		super(repository);
+	}
+
+	@Override
+	public String toString() {
+		return "RefsChangedEvent[" + getRepository() + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 04d9b13..6f78652 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -49,7 +49,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
+import java.util.Vector;
 
 import org.spearce.jgit.errors.IncorrectObjectTypeException;
 import org.spearce.jgit.errors.RevisionSyntaxException;
@@ -92,6 +94,8 @@ public class Repository {
 
 	private GitIndex index;
 
+	private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
+
 	/**
 	 * Construct a representation of a Git repository.
 	 * 
@@ -1028,4 +1032,41 @@ public class Repository {
 	public File getWorkDir() {
 		return getDirectory().getParentFile();
 	}
+
+	/**
+	 * Register a {@link RepositoryListener} which will be notified
+	 * when ref changes are detected.
+	 *
+	 * @param l
+	 */
+	public void addRepositoryChangedListener(final RepositoryListener l) {
+		listeners.add(l);
+	}
+
+	/**
+	 * Remove a registered {@link RepositoryListener}
+	 * @param l
+	 */
+	public void removeRepositoryChangedListener(final RepositoryListener l) {
+		listeners.remove(l);
+	}
+
+	void fireRefsMaybeChanged() {
+		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
+			refs.lastNotifiedRefModification = refs.lastRefModification;
+			final RefsChangedEvent event = new RefsChangedEvent(this);
+			for (final RepositoryListener l :
+				listeners.toArray(new RepositoryListener[listeners.size()])) {
+				l.refsChanged(event);
+			}
+		}
+	}
+
+	void fireIndexChanged() {
+		final IndexChangedEvent event = new IndexChangedEvent(this);
+		for (final RepositoryListener l :
+			listeners.toArray(new RepositoryListener[listeners.size()])) {
+			l.indexChanged(event);
+		}
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
new file mode 100644
index 0000000..d1ff07d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * A default {@link RepositoryListener} that does nothing except invoke an
+ * optional general method for any repository change.
+ */
+public class RepositoryAdapter implements RepositoryListener {
+
+	public void indexChanged(final IndexChangedEvent e) {
+		// Empty
+	}
+
+	public void refsChanged(final RefsChangedEvent e) {
+		// Empty
+	}
+
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
new file mode 100644
index 0000000..b58df87
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * This class passes information about changed refs to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RepositoryChangedEvent {
+	private final Repository repository;
+
+	RepositoryChangedEvent(final Repository repository) {
+		this.repository = repository;
+	}
+
+	/**
+	 * @return the repository that was changed
+	 */
+	public Repository getRepository() {
+		return repository;
+	}
+
+	@Override
+	public String toString() {
+		return "RepositoryChangedEvent[" + repository + "]";
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java
new file mode 100644
index 0000000..ceb14ce
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.lib;
+
+/**
+ * A RepositoryListener gets notification about changes in refs or repository.
+ * <p>
+ * It currently does <em>not</em> get notification about which items are
+ * changed.
+ */
+public interface RepositoryListener {
+	/**
+	 * Invoked when a ref changes
+	 *
+	 * @param e
+	 *            information about the changes.
+	 */
+	void refsChanged(RefsChangedEvent e);
+
+	/**
+	 * Invoked when the index changes
+	 *
+	 * @param e
+	 *            information about the changes.
+	 */
+	void indexChanged(IndexChangedEvent e);
+
+}
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-11 09:32

Subject: [PATCH 7/7] Add a job to refresh projects when the index changes.
Message-ID: <1215768745-1420-1-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215768745-1420-1-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <20080711043317.GD32633@spearce.org>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/egit/ui/Activator.java         |   85 +++++++++++++++++++-
 1 files changed, 81 insertions(+), 4 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 3e02c44..89e6579 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
@@ -11,16 +11,20 @@ package org.spearce.egit.ui;
 import java.net.Authenticator;
 import java.net.ProxySelector;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 import org.eclipse.core.net.proxy.IProxyService;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jsch.core.IJSchService;
@@ -30,7 +34,10 @@ import org.eclipse.ui.themes.ITheme;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.spearce.egit.core.project.RepositoryMapping;
+import org.spearce.jgit.lib.IndexChangedEvent;
+import org.spearce.jgit.lib.RefsChangedEvent;
 import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.RepositoryListener;
 import org.spearce.jgit.transport.SshSessionFactory;
 
 /**
@@ -137,6 +144,7 @@ public class Activator extends AbstractUIPlugin {
 
 	private boolean traceVerbose;
 	private RCS rcs;
+	private RIRefresh refreshJob;
 
 	/**
 	 * Constructor for the egit ui plugin singleton
@@ -151,6 +159,71 @@ public class Activator extends AbstractUIPlugin {
 		setupSSH(context);
 		setupProxy(context);
 		setupRepoChangeScanner();
+		setupRepoIndexRefresh();
+	}
+
+	private void setupRepoIndexRefresh() {
+		refreshJob = new RIRefresh();
+		Repository.addAnyRepositoryChangedListener(refreshJob);
+	}
+
+	static class RIRefresh extends Job implements RepositoryListener {
+
+		RIRefresh() {
+			super("Git index refresh Job");
+		}
+
+		private Set<IProject> projectsToScan = new LinkedHashSet<IProject>();
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+			monitor.beginTask("Refreshing git managed projects", projects.length);
+
+			while (projectsToScan.size() > 0) {
+				IProject p;
+				synchronized (projectsToScan) {
+					if (projectsToScan.size() == 0)
+						break;
+					Iterator<IProject> i = projectsToScan.iterator();
+					p = i.next();
+					i.remove();
+				}
+				ISchedulingRule rule = p.getWorkspace().getRuleFactory().refreshRule(p);
+				try {
+					getJobManager().beginRule(rule, monitor);
+					p.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 1));
+				} catch (CoreException e) {
+					logError("Failed to refresh projects from index changes", e);
+					return new Status(IStatus.ERROR, getPluginId(), e.getMessage());
+				} finally {
+					getJobManager().endRule(rule);
+				}
+			}
+			monitor.done();
+			return Status.OK_STATUS;
+		}
+
+		public void indexChanged(IndexChangedEvent e) {
+			IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+			Set<IProject> toRefresh= new HashSet<IProject>();
+			for (IProject p : projects) {
+				RepositoryMapping mapping = RepositoryMapping.getMapping(p);
+				if (mapping != null && mapping.getRepository() == e.getRepository()) {
+					toRefresh.add(p);
+				}
+			}
+			synchronized (projectsToScan) {
+				projectsToScan.addAll(toRefresh);
+			}
+			if (projectsToScan.size() > 0)
+				schedule();
+		}
+
+		public void refsChanged(RefsChangedEvent e) {
+			// Do not react here
+		}
+
 	}
 
 	static class RCS extends Job {
@@ -236,10 +309,14 @@ public class Activator extends AbstractUIPlugin {
 
 	public void stop(final BundleContext context) throws Exception {
 		trace("Trying to cancel " + rcs.getName() + " job");
-		if (!rcs.cancel()) {
-			rcs.join();
-		}
-		trace("rcs.getName() " + rcs.getName() + " cancelled ok");
+		rcs.cancel();
+		trace("Trying to cancel " + refreshJob.getName() + " job");
+		refreshJob.cancel();
+
+		rcs.join();
+		refreshJob.join();
+
+		trace("Jobs terminated");
 		super.stop(context);
 		plugin = null;
 	}
-- 
1.5.6.2.220.g44701

```

## Robin Rosenberg, 2008-07-11 09:48

Subject: [PATCH 4/4] Add a method to listen to changes in any repository
Message-ID: <1215769719-5238-1-git-send-email-robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/1215769719-5238-1-git-send-email-robin.rosenberg%40dewire.com
In-Reply-To: <20080711042822.GC32633@spearce.org>

```
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |   39 ++++++++++++++++++--
 1 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 52f6750..af860b3 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -95,6 +95,7 @@ public class Repository {
 	private GitIndex index;
 
 	private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
+	static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
 
 	/**
 	 * Construct a representation of a Git repository.
@@ -1051,12 +1052,36 @@ public class Repository {
 		listeners.remove(l);
 	}
 
+	/**
+	 * Register a global {@link RepositoryListener} which will be notified
+	 * when a ref changes in any repository are detected.
+	 *
+	 * @param l
+	 */
+	public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
+		allListeners.add(l);
+	}
+
+	/**
+	 * Remove a globally registered {@link RepositoryListener}
+	 * @param l
+	 */
+	public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
+		allListeners.remove(l);
+	}
+
 	void fireRefsMaybeChanged() {
 		if (refs.lastRefModification != refs.lastNotifiedRefModification) {
 			refs.lastNotifiedRefModification = refs.lastRefModification;
 			final RefsChangedEvent event = new RefsChangedEvent(this);
-			for (final RepositoryListener l :
-				listeners.toArray(new RepositoryListener[0])) {
+			List<RepositoryListener> all;
+			synchronized (listeners) {
+				all = new ArrayList<RepositoryListener>(listeners);
+			}
+			synchronized (allListeners) {
+				all.addAll(allListeners);
+			}
+			for (final RepositoryListener l : all) {
 				l.refsChanged(event);
 			}
 		}
@@ -1064,8 +1089,14 @@ public class Repository {
 
 	void fireIndexChanged() {
 		final IndexChangedEvent event = new IndexChangedEvent(this);
-		for (final RepositoryListener l :
-			listeners.toArray(new RepositoryListener[0])) {
+		List<RepositoryListener> all;
+		synchronized (listeners) {
+			all = new ArrayList<RepositoryListener>(listeners);
+		}
+		synchronized (allListeners) {
+			all.addAll(allListeners);
+		}
+		for (final RepositoryListener l : all) {
 			l.indexChanged(event);
 		}
 	}
-- 
1.5.6.2.220.g44701

```

## Andreas Ericsson, 2008-07-11 12:24

Subject: jgit (was: [PATCH 4/4] Add a method...)
Message-ID: <487750E3.90303@op5.se>
URL: https://gitlist.dev/e/487750E3.90303%40op5.se
In-Reply-To: <1215769719-5238-1-git-send-email-robin.rosenberg@dewire.com>

```
Sorry for butting in, but I'd appreciate if you kept adding 'jgit'
to the subject so I can keep filtering these patches out. The volume
on git@vger is high enough without having to follow off-shoots that
are of no particular concern to me (although I certainly applaud the
effort).

Thanks

Robin Rosenberg wrote:
[ a patch for jgit ]

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

```

## Robin Rosenberg, 2008-07-11 12:24

Subject: Re: jgit (was: [PATCH 4/4] Add a method...)
Message-ID: <200807111424.14328.robin.rosenberg@dewire.com>
URL: https://gitlist.dev/e/200807111424.14328.robin.rosenberg%40dewire.com
In-Reply-To: <487750E3.90303@op5.se>

```
fredagen den 11 juli 2008 14.24.03 skrev Andreas Ericsson:
> Sorry for butting in, but I'd appreciate if you kept adding 'jgit'
> to the subject so I can keep filtering these patches out. The volume
> on git@vger is high enough without having to follow off-shoots that
> are of no particular concern to me (although I certainly applaud the
> effort).

It was an accident. Honestly :)

-- robin

```
