{"thread":{"id":"14400","subject":"[EGIT PATCH 0/9] Repository change listeners","startedAt":"2008-07-10T22:40:42Z","lastAt":"2008-07-11T12:24:14Z","messageCount":19,"participants":["Robin Rosenberg","Shawn O. Pearce","Andreas Ericsson"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"82867","messageId":"1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":null,"subject":"[EGIT PATCH 0/9] Repository change listeners","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:42Z","receivedAt":"2008-07-10T22:40:42Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"We want to make the UI react on changes to the repo, but the parts\nof the code that makes the changes doesn't know who wants to react\non them. This adds a publish-subcribe mechanism, including detection\nof externally made changes, e.g. by C Git.\n\n-- robin\n\nRobin Rosenberg (9):\n  Create a listener structure for changes to refs and index\n  Cached modification times for symbolic refs too\n  Connect the history page to the refs update subscription mechanism\n  Add a method to listen to changes in any repository\n  Add a job to periodically scan for repository changes\n  Change GitHistoryPage to listen on any repository.\n  Add a job to refresh projects when the index changes.\n  Make git dectected changes depend on the automatic refresh setting\n  Attach the resource decorator to the repository change event\n    mechanism\n\n .../src/org/spearce/egit/ui/Activator.java         |  155 ++++++++++++++++++++\n .../internal/decorators/GitResourceDecorator.java  |   29 ++++-\n .../egit/ui/internal/history/GitHistoryPage.java   |   43 +++++-\n .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +\n .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   63 ++++++---\n .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++\n .../src/org/spearce/jgit/lib/Repository.java       |   74 ++++++++++\n .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 +++++++\n .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++\n .../org/spearce/jgit/lib/RepositoryListener.java   |   63 ++++++++\n 11 files changed, 635 insertions(+), 23 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\n"},{"id":"82876","messageId":"1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 1/9] Create a listener structure for changes to refs and index","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:43Z","receivedAt":"2008-07-10T22:40:43Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"This version does not tell you which refs have changed, nor\nwhat changes have happened. There is not scanning for externally\ninitiated changes either, though such changes can be found when\na JGit client wants to read index or refs information.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../egit/ui/internal/history/GitHistoryPage.java   |   31 +++++++++-\n .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +\n .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++++++++++++\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   17 +++++\n .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++++++++++++\n .../src/org/spearce/jgit/lib/Repository.java       |   41 +++++++++++++\n .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 ++++++++++++++++\n .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++++++++++++++\n .../org/spearce/jgit/lib/RepositoryListener.java   |   63 +++++++++++++++++++\n 9 files changed, 382 insertions(+), 1 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\n\ndiff --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\nindex 6b55185..7e2f726 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n@@ -61,7 +61,10 @@ import org.spearce.egit.ui.UIIcons;\n import org.spearce.egit.ui.UIPreferences;\n import org.spearce.egit.ui.UIText;\n import org.spearce.jgit.lib.AnyObjectId;\n+import org.spearce.jgit.lib.IndexChangedEvent;\n+import org.spearce.jgit.lib.RefsChangedEvent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryListener;\n import org.spearce.jgit.revplot.PlotCommit;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevFlag;\n@@ -73,7 +76,7 @@ import org.spearce.jgit.treewalk.filter.PathFilterGroup;\n import org.spearce.jgit.treewalk.filter.TreeFilter;\n \n /** Graphical commit history viewer. */\n-public class GitHistoryPage extends HistoryPage {\n+public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \tprivate static final String PREF_COMMENT_WRAP = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP;\n \n \tprivate static final String PREF_COMMENT_FILL = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL;\n@@ -230,6 +233,32 @@ public class GitHistoryPage extends HistoryPage {\n \t\tlayout();\n \t}\n \n+\tprivate Runnable refschangedRunnable;\n+\n+\tpublic void refsChanged(final RefsChangedEvent e) {\n+\t\tif (getControl().isDisposed())\n+\t\t\treturn;\n+\n+\t\tsynchronized (this) {\n+\t\t\tif (refschangedRunnable == null) {\n+\t\t\t\trefschangedRunnable = new Runnable() {\n+\t\t\t\t\tpublic void run() {\n+\t\t\t\t\t\tif (!getControl().isDisposed()) {\n+\t\t\t\t\t\t\tActivator.trace(\"Executing async repository changed event\");\n+\t\t\t\t\t\t\trefschangedRunnable = null;\n+\t\t\t\t\t\t\tinputSet();\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t};\n+\t\t\t\tgetControl().getDisplay().asyncExec(refschangedRunnable);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tpublic void indexChanged(final IndexChangedEvent e) {\n+\t\t// We do not use index information here now\n+\t}\n+\n \tprivate void finishContextMenu() {\n \t\tpopupMgr.add(new Separator());\n \t\tpopupMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\nindex 5be404e..c7a4402 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\n@@ -146,6 +146,7 @@ public class GitIndex {\n \tpublic void rereadIfNecessary() throws IOException {\n \t\tif (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {\n \t\t\tread();\n+\t\t\tdb.fireIndexChanged();\n \t\t}\n \t}\n \n@@ -269,6 +270,8 @@ public class GitIndex {\n \t\t\t\t\t\t\"Could not rename temporary index file to index\");\n \t\t\tchanged = false;\n \t\t\tstatDirty = false;\n+\t\t\tlastCacheTime = cacheFile.lastModified();\n+\t\t\tdb.fireIndexChanged();\n \t\t} finally {\n \t\t\tif (!lock.delete())\n \t\t\t\tthrow new IOException(\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\nnew file mode 100644\nindex 0000000..30a40d1\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\n@@ -0,0 +1,55 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about a changed Git index to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class IndexChangedEvent extends RepositoryChangedEvent {\n+\tIndexChangedEvent(final Repository repository) {\n+\t\tsuper(repository);\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"IndexChangedEvent[\" + getRepository() + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\nindex 9e3e020..4be33b8 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n@@ -81,6 +81,10 @@ class RefDatabase {\n \n \tprivate long packedRefsLength;\n \n+\tlong lastRefModification;\n+\n+\tlong lastNotifiedRefModification;\n+\n \tRefDatabase(final Repository r) {\n \t\tdb = r;\n \t\tgitDir = db.getDirectory();\n@@ -132,6 +136,8 @@ class RefDatabase {\n \n \tvoid stored(final String name, final ObjectId id, final long time) {\n \t\tlooseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));\n+\t\tsetModified();\n+\t\tdb.fireRefsMaybeChanged();\n \t}\n \n \t/**\n@@ -155,6 +161,12 @@ class RefDatabase {\n \t\t}\n \t\tif (!lck.commit())\n \t\t\tthrow new ObjectWritingException(\"Unable to write \" + name);\n+\t\tsetModified();\n+\t\tdb.fireRefsMaybeChanged();\n+\t}\n+\n+\tvoid setModified() {\n+\t\tlastRefModification = System.currentTimeMillis();\n \t}\n \n \tRef readRef(final String partialName) throws IOException {\n@@ -192,6 +204,7 @@ class RefDatabase {\n \t\treadPackedRefs(avail);\n \t\treadLooseRefs(avail, REFS_SLASH, refsDir);\n \t\treadOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));\n+\t\tdb.fireRefsMaybeChanged();\n \t\treturn avail;\n \t}\n \n@@ -321,6 +334,8 @@ class RefDatabase {\n \t\t\treturn r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);\n \t\t}\n \n+\t\tsetModified();\n+\n \t\tfinal ObjectId id;\n \t\ttry {\n \t\t\tid = ObjectId.fromString(line);\n@@ -378,6 +393,7 @@ class RefDatabase {\n \t\t\tpackedRefsLastModified = currTime;\n \t\t\tpackedRefsLength = currLen;\n \t\t\tpackedRefs = newPackedRefs;\n+\t\t\tsetModified();\n \t\t} catch (FileNotFoundException noPackedRefs) {\n \t\t\t// Ignore it and leave the new map empty.\n \t\t\t//\n@@ -414,4 +430,5 @@ class RefDatabase {\n \t\t\tlastModified = mtime;\n \t\t}\n \t}\n+\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\nnew file mode 100644\nindex 0000000..c8936c7\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\n@@ -0,0 +1,55 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about a changed Git index to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class RefsChangedEvent extends RepositoryChangedEvent {\n+\tRefsChangedEvent(final Repository repository) {\n+\t\tsuper(repository);\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"RefsChangedEvent[\" + getRepository() + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 04d9b13..6f78652 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -49,7 +49,9 @@ import java.util.ArrayList;\n import java.util.Collection;\n import java.util.HashMap;\n import java.util.LinkedList;\n+import java.util.List;\n import java.util.Map;\n+import java.util.Vector;\n \n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n import org.spearce.jgit.errors.RevisionSyntaxException;\n@@ -92,6 +94,8 @@ public class Repository {\n \n \tprivate GitIndex index;\n \n+\tprivate List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe\n+\n \t/**\n \t * Construct a representation of a Git repository.\n \t * \n@@ -1028,4 +1032,41 @@ public class Repository {\n \tpublic File getWorkDir() {\n \t\treturn getDirectory().getParentFile();\n \t}\n+\n+\t/**\n+\t * Register a {@link RepositoryListener} which will be notified\n+\t * when ref changes are detected.\n+\t *\n+\t * @param l\n+\t */\n+\tpublic void addRepositoryChangedListener(final RepositoryListener l) {\n+\t\tlisteners.add(l);\n+\t}\n+\n+\t/**\n+\t * Remove a registered {@link RepositoryListener}\n+\t * @param l\n+\t */\n+\tpublic void removeRepositoryChangedListener(final RepositoryListener l) {\n+\t\tlisteners.remove(l);\n+\t}\n+\n+\tvoid fireRefsMaybeChanged() {\n+\t\tif (refs.lastRefModification != refs.lastNotifiedRefModification) {\n+\t\t\trefs.lastNotifiedRefModification = refs.lastRefModification;\n+\t\t\tfinal RefsChangedEvent event = new RefsChangedEvent(this);\n+\t\t\tfor (final RepositoryListener l :\n+\t\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\t\t\tl.refsChanged(event);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tvoid fireIndexChanged() {\n+\t\tfinal IndexChangedEvent event = new IndexChangedEvent(this);\n+\t\tfor (final RepositoryListener l :\n+\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\t\tl.indexChanged(event);\n+\t\t}\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\nnew file mode 100644\nindex 0000000..d1ff07d\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\n@@ -0,0 +1,54 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * A default {@link RepositoryListener} that does nothing except invoke an\n+ * optional general method for any repository change.\n+ */\n+public class RepositoryAdapter implements RepositoryListener {\n+\n+\tpublic void indexChanged(final IndexChangedEvent e) {\n+\t\t// Empty\n+\t}\n+\n+\tpublic void refsChanged(final RefsChangedEvent e) {\n+\t\t// Empty\n+\t}\n+\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\nnew file mode 100644\nindex 0000000..b58df87\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\n@@ -0,0 +1,64 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about changed refs to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class RepositoryChangedEvent {\n+\tprivate final Repository repository;\n+\n+\tRepositoryChangedEvent(final Repository repository) {\n+\t\tthis.repository = repository;\n+\t}\n+\n+\t/**\n+\t * @return the repository that was changed\n+\t */\n+\tpublic Repository getRepository() {\n+\t\treturn repository;\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"RepositoryChangedEvent[\" + repository + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\nnew file mode 100644\nindex 0000000..ceb14ce\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\n@@ -0,0 +1,63 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * A RepositoryListener gets notification about changes in refs or repository.\n+ * <p>\n+ * It currently does <em>not</em> get notification about which items are\n+ * changed.\n+ */\n+public interface RepositoryListener {\n+\t/**\n+\t * Invoked when a ref changes\n+\t *\n+\t * @param e\n+\t *            information about the changes.\n+\t */\n+\tvoid refsChanged(RefsChangedEvent e);\n+\n+\t/**\n+\t * Invoked when the index changes\n+\t *\n+\t * @param e\n+\t *            information about the changes.\n+\t */\n+\tvoid indexChanged(IndexChangedEvent e);\n+\n+}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82869","messageId":"1215729651-26781-3-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 2/9] Cached modification times for symbolic refs too","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:44Z","receivedAt":"2008-07-10T22:40:44Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"We want to detect changes to symbolic refs like HEAD. When HEAD is\nredirected to another branch, that's a change even if if the branch\nhead itself did not change.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   48 +++++++++++---------\n 1 files changed, 27 insertions(+), 21 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\nindex 4be33b8..17a74e5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n@@ -71,7 +71,8 @@ class RefDatabase {\n \n \tprivate final File refsDir;\n \n-\tprivate Map<String, CachedRef> looseRefs;\n+\tprivate Map<String, Ref> looseRefs;\n+\tprivate Map<String, Long> looseRefsMTime;\n \n \tprivate final File packedRefsFile;\n \n@@ -94,7 +95,8 @@ class RefDatabase {\n \t}\n \n \tvoid clearCache() {\n-\t\tlooseRefs = new HashMap<String, CachedRef>();\n+\t\tlooseRefs = new HashMap<String, Ref>();\n+\t\tlooseRefsMTime = new HashMap<String, Long>();\n \t\tpackedRefs = new HashMap<String, Ref>();\n \t\tpackedRefsLastModified = 0;\n \t\tpackedRefsLength = 0;\n@@ -135,7 +137,8 @@ class RefDatabase {\n \t}\n \n \tvoid stored(final String name, final ObjectId id, final long time) {\n-\t\tlooseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));\n+\t\tlooseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, id));\n+\t\tlooseRefsMTime.put(name, time);\n \t\tsetModified();\n \t\tdb.fireRefsMaybeChanged();\n \t}\n@@ -203,7 +206,11 @@ class RefDatabase {\n \t\tfinal HashMap<String, Ref> avail = new HashMap<String, Ref>();\n \t\treadPackedRefs(avail);\n \t\treadLooseRefs(avail, REFS_SLASH, refsDir);\n-\t\treadOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));\n+\t\ttry {\n+\t\t\tavail.put(Constants.HEAD, readRefBasic(Constants.HEAD, 0));\n+\t\t} catch (IOException e) {\n+\t\t\t// ignore here\n+\t\t}\n \t\tdb.fireRefsMaybeChanged();\n \t\treturn avail;\n \t}\n@@ -231,13 +238,15 @@ class RefDatabase {\n \t\t\tfinal String refName, final File ent) {\n \t\t// Unchanged and cached? Don't read it again.\n \t\t//\n-\t\tCachedRef ref = looseRefs.get(refName);\n+\t\tRef ref = looseRefs.get(refName);\n \t\tif (ref != null) {\n-\t\t\tif (ref.lastModified == ent.lastModified()) {\n+\t\t\tLong cachedlastModified = looseRefsMTime.get(refName);\n+\t\t\tif (cachedlastModified != null && cachedlastModified == ent.lastModified()) {\n \t\t\t\tavail.put(ref.getName(), ref);\n \t\t\t\treturn;\n \t\t\t}\n \t\t\tlooseRefs.remove(refName);\n+\t\t\tlooseRefsMTime.remove(refName);\n \t\t}\n \n \t\t// Recurse into the directory.\n@@ -269,9 +278,9 @@ class RefDatabase {\n \t\t\t\t\treturn;\n \t\t\t\t}\n \n-\t\t\t\tref = new CachedRef(Ref.Storage.LOOSE, refName, id, ent\n-\t\t\t\t\t\t.lastModified());\n+\t\t\t\tref = new Ref(Ref.Storage.LOOSE, refName, id);\n \t\t\t\tlooseRefs.put(ref.getName(), ref);\n+\t\t\t\tlooseRefsMTime.put(ref.getName(), ent.lastModified());\n \t\t\t\tavail.put(ref.getName(), ref);\n \t\t\t} finally {\n \t\t\t\tin.close();\n@@ -297,13 +306,15 @@ class RefDatabase {\n \t\t// Prefer loose ref to packed ref as the loose\n \t\t// file can be more up-to-date than a packed one.\n \t\t//\n-\t\tCachedRef ref = looseRefs.get(name);\n+\t\tRef ref = looseRefs.get(name);\n \t\tfinal File loose = fileForRef(name);\n \t\tfinal long mtime = loose.lastModified();\n \t\tif (ref != null) {\n-\t\t\tif (ref.lastModified == mtime)\n+\t\t\tLong cachedlastModified = looseRefsMTime.get(name);\n+\t\t\tif (cachedlastModified != null && cachedlastModified == mtime)\n \t\t\t\treturn ref;\n \t\t\tlooseRefs.remove(name);\n+\t\t\tlooseRefsMTime.remove(name);\n \t\t}\n \n \t\tif (mtime == 0) {\n@@ -331,6 +342,10 @@ class RefDatabase {\n \n \t\t\tfinal String target = line.substring(\"ref: \".length());\n \t\t\tfinal Ref r = readRefBasic(target, depth + 1);\n+\t\t\tLong cachedMtime = looseRefsMTime.get(name);\n+\t\t\tif (cachedMtime != null && cachedMtime != mtime)\n+\t\t\t\tsetModified();\n+\t\t\tlooseRefsMTime.put(name, mtime);\n \t\t\treturn r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);\n \t\t}\n \n@@ -343,8 +358,9 @@ class RefDatabase {\n \t\t\tthrow new IOException(\"Not a ref: \" + name + \": \" + line);\n \t\t}\n \n-\t\tref = new CachedRef(Ref.Storage.LOOSE, name, id, mtime);\n+\t\tref = new Ref(Ref.Storage.LOOSE, name, id);\n \t\tlooseRefs.put(name, ref);\n+\t\tlooseRefsMTime.put(name, mtime);\n \t\treturn ref;\n \t}\n \n@@ -421,14 +437,4 @@ class RefDatabase {\n \t\t\t\tfileLocation), CHAR_ENC));\n \t}\n \n-\tprivate static class CachedRef extends Ref {\n-\t\tfinal long lastModified;\n-\n-\t\tCachedRef(final Storage st, final String refName, final ObjectId id,\n-\t\t\t\tfinal long mtime) {\n-\t\t\tsuper(st, refName, id);\n-\t\t\tlastModified = mtime;\n-\t\t}\n-\t}\n-\n }\n-- \n1.5.6.2.220.g44701\n"},{"id":"82872","messageId":"1215729651-26781-4-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-3-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 3/9] Connect the history page to the refs update subscription mechanism","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:45Z","receivedAt":"2008-07-10T22:40:45Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Now the history page can get updated automatically without polling.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../egit/ui/internal/history/GitHistoryPage.java   |   11 ++++++++++-\n 1 files changed, 10 insertions(+), 1 deletions(-)\n\ndiff --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\nindex 7e2f726..418f3b6 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n@@ -168,6 +168,9 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t/** Last HEAD */\n \tprivate AnyObjectId currentHeadId;\n \n+\t/** We need to remember the current repository */\n+\tprivate Repository db;\n+\n \t/**\n \t * Highlight flag that can be applied to commits to make them stand out.\n \t * <p>\n@@ -536,6 +539,9 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\t\trevObjectSelectionProvider.setActiveRepository(null);\n \t\tcancelRefreshJob();\n \n+\t\tif (db != null)\n+\t\t\tdb.removeRepositoryChangedListener(this);\n+\n \t\tif (graph == null)\n \t\t\treturn false;\n \n@@ -543,7 +549,8 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\tif (in == null || in.length == 0)\n \t\t\treturn false;\n \n-\t\tRepository db = null;\n+\t\tdb = null;\n+\n \t\tfinal ArrayList<String> paths = new ArrayList<String>(in.length);\n \t\tfor (final IResource r : in) {\n \t\t\tfinal RepositoryMapping map = RepositoryMapping.getMapping(r);\n@@ -563,6 +570,8 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\tif (db == null)\n \t\t\treturn false;\n \n+\t\tdb.addRepositoryChangedListener(this);\n+\n \t\tfinal AnyObjectId headId;\n \t\ttry {\n \t\t\theadId = db.resolve(\"HEAD\");\n-- \n1.5.6.2.220.g44701\n"},{"id":"82868","messageId":"1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-4-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 4/9] Add a method to listen to changes in any repository","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:46Z","receivedAt":"2008-07-10T22:40:46Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |   31 +++++++++++++++++--\n 1 files changed, 27 insertions(+), 4 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 6f78652..dfa3045 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -95,6 +95,7 @@ public class Repository {\n \tprivate GitIndex index;\n \n \tprivate List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe\n+\tstatic private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe\n \n \t/**\n \t * Construct a representation of a Git repository.\n@@ -1051,12 +1052,32 @@ public class Repository {\n \t\tlisteners.remove(l);\n \t}\n \n+\t/**\n+\t * Register a global {@link RepositoryListener} which will be notified\n+\t * when a ref changes in any repository are detected.\n+\t *\n+\t * @param l\n+\t */\n+\tpublic static void addAnyRepositoryChangedListener(final RepositoryListener l) {\n+\t\tallListeners.add(l);\n+\t}\n+\n+\t/**\n+\t * Remove a globally registered {@link RepositoryListener}\n+\t * @param l\n+\t */\n+\tpublic static void removeAnyRepositoryChangedListener(final RepositoryListener l) {\n+\t\tallListeners.remove(l);\n+\t}\n+\n \tvoid fireRefsMaybeChanged() {\n \t\tif (refs.lastRefModification != refs.lastNotifiedRefModification) {\n \t\t\trefs.lastNotifiedRefModification = refs.lastRefModification;\n \t\t\tfinal RefsChangedEvent event = new RefsChangedEvent(this);\n-\t\t\tfor (final RepositoryListener l :\n-\t\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\t\tList<RepositoryListener> all = new ArrayList<RepositoryListener>(\n+\t\t\t\t\tlisteners);\n+\t\t\tall.addAll(allListeners);\n+\t\t\tfor (final RepositoryListener l : all) {\n \t\t\t\tl.refsChanged(event);\n \t\t\t}\n \t\t}\n@@ -1064,8 +1085,10 @@ public class Repository {\n \n \tvoid fireIndexChanged() {\n \t\tfinal IndexChangedEvent event = new IndexChangedEvent(this);\n-\t\tfor (final RepositoryListener l :\n-\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\tList<RepositoryListener> all = new ArrayList<RepositoryListener>(\n+\t\t\t\tlisteners);\n+\t\tall.addAll(allListeners);\n+\t\tfor (final RepositoryListener l : all) {\n \t\t\tl.indexChanged(event);\n \t\t}\n \t}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82871","messageId":"1215729651-26781-6-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 5/9] Add a job to periodically scan for repository changes","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:47Z","receivedAt":"2008-07-10T22:40:47Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/egit/ui/Activator.java         |   75 ++++++++++++++++++++\n .../src/org/spearce/jgit/lib/Repository.java       |   10 +++\n 2 files changed, 85 insertions(+), 0 deletions(-)\n\ndiff --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\nindex 8d1b8cd..3e02c44 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n@@ -10,18 +10,27 @@ package org.spearce.egit.ui;\n \n import java.net.Authenticator;\n import java.net.ProxySelector;\n+import java.util.HashSet;\n+import java.util.Set;\n \n import org.eclipse.core.net.proxy.IProxyService;\n+import org.eclipse.core.resources.IProject;\n+import org.eclipse.core.resources.ResourcesPlugin;\n import org.eclipse.core.runtime.CoreException;\n+import org.eclipse.core.runtime.IProgressMonitor;\n import org.eclipse.core.runtime.IStatus;\n import org.eclipse.core.runtime.Platform;\n import org.eclipse.core.runtime.Status;\n+import org.eclipse.core.runtime.jobs.ISchedulingRule;\n+import org.eclipse.core.runtime.jobs.Job;\n import org.eclipse.jsch.core.IJSchService;\n import org.eclipse.swt.graphics.Font;\n import org.eclipse.ui.plugin.AbstractUIPlugin;\n import org.eclipse.ui.themes.ITheme;\n import org.osgi.framework.BundleContext;\n import org.osgi.framework.ServiceReference;\n+import org.spearce.egit.core.project.RepositoryMapping;\n+import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.transport.SshSessionFactory;\n \n /**\n@@ -127,6 +136,7 @@ public class Activator extends AbstractUIPlugin {\n \t}\n \n \tprivate boolean traceVerbose;\n+\tprivate RCS rcs;\n \n \t/**\n \t * Constructor for the egit ui plugin singleton\n@@ -140,6 +150,66 @@ public class Activator extends AbstractUIPlugin {\n \t\ttraceVerbose = isOptionSet(\"/trace/verbose\");\n \t\tsetupSSH(context);\n \t\tsetupProxy(context);\n+\t\tsetupRepoChangeScanner();\n+\t}\n+\n+\tstatic class RCS extends Job {\n+\t\tRCS() {\n+\t\t\tsuper(\"Repository Change Scanner\");\n+\t\t}\n+\n+\t\t// FIXME, need to be more intelligent about this to avoid too much work\n+\t\tprivate static final long REPO_SCAN_INTERVAL = 10000L;\n+\n+\t\t@Override\n+\t\tprotected IStatus run(IProgressMonitor monitor) {\n+\t\t\ttry {\n+\t\t\t\t// A repository can contain many projects, only scan once\n+\t\t\t\t// (a project could in theory be distributed among many\n+\t\t\t\t// repositories. We discard that as being ugly and stupid for\n+\t\t\t\t// the moment.\n+\t\t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n+\t\t\t\tmonitor.beginTask(\"Scanning Git repositories for changes\", projects.length);\n+\t\t\t\tSet<Repository> scanned = new HashSet<Repository>();\n+\t\t\t\tfor (IProject p : projects) {\n+\t\t\t\t\tRepositoryMapping mapping = RepositoryMapping.getMapping(p);\n+\t\t\t\t\tif (mapping != null) {\n+\t\t\t\t\t\tRepository r = mapping.getRepository();\n+\t\t\t\t\t\tif (!scanned.contains(r)) {\n+\t\t\t\t\t\t\tif (monitor.isCanceled())\n+\t\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\t\ttrace(\"Scanning \" + r + \" for changes\");\n+\t\t\t\t\t\t\tscanned.add(r);\n+\t\t\t\t\t\t\tISchedulingRule rule = p.getWorkspace().getRuleFactory().modifyRule(p);\n+\t\t\t\t\t\t\tgetJobManager().beginRule(rule, monitor);\n+\t\t\t\t\t\t\ttry {\n+\t\t\t\t\t\t\t\tr.scanForRepoChanges();\n+\t\t\t\t\t\t\t} finally {\n+\t\t\t\t\t\t\t\tgetJobManager().endRule(rule);\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t\tmonitor.worked(1);\n+\t\t\t\t}\n+\t\t\t\tmonitor.done();\n+\t\t\t\ttrace(\"Rescheduling \" + getName() + \" job\");\n+\t\t\t\tschedule(REPO_SCAN_INTERVAL);\n+\t\t\t} catch (Exception e) {\n+\t\t\t\ttrace(\"Stopped rescheduling \" + getName() + \"job\");\n+\t\t\t\treturn new Status(\n+\t\t\t\t\t\tIStatus.ERROR,\n+\t\t\t\t\t\tgetPluginId(),\n+\t\t\t\t\t\t0,\n+\t\t\t\t\t\t\"An error occurred while scanning for changes. Scanning aborted\",\n+\t\t\t\t\t\te);\n+\t\t\t}\n+\t\t\treturn Status.OK_STATUS;\n+\t\t}\n+\t}\n+\n+\tprivate void setupRepoChangeScanner() {\n+\t\trcs = new RCS();\n+\t\trcs.schedule(RCS.REPO_SCAN_INTERVAL);\n \t}\n \n \tprivate void setupSSH(final BundleContext context) {\n@@ -165,6 +235,11 @@ public class Activator extends AbstractUIPlugin {\n \t}\n \n \tpublic void stop(final BundleContext context) throws Exception {\n+\t\ttrace(\"Trying to cancel \" + rcs.getName() + \" job\");\n+\t\tif (!rcs.cancel()) {\n+\t\t\trcs.join();\n+\t\t}\n+\t\ttrace(\"rcs.getName() \" + rcs.getName() + \" cancelled ok\");\n \t\tsuper.stop(context);\n \t\tplugin = null;\n \t}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex dfa3045..9b65154 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -1092,4 +1092,14 @@ public class Repository {\n \t\t\tl.indexChanged(event);\n \t\t}\n \t}\n+\n+\t/**\n+\t * Force a scan for changed refs.\n+\t *\n+\t * @throws IOException\n+\t */\n+\tpublic void scanForRepoChanges() throws IOException {\n+\t\tgetAllRefs(); // This will look for changes to refs\n+\t\tgetIndex(); // This will detect changes in the index\n+\t}\n }\n-- \n1.5.6.2.220.g44701\n"},{"id":"82875","messageId":"1215729651-26781-7-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-6-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 6/9] Change GitHistoryPage to listen on any repository.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:48Z","receivedAt":"2008-07-10T22:40:48Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"This makes listening simpler.\n---\n .../egit/ui/internal/history/GitHistoryPage.java   |   11 ++++++-----\n 1 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --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\nindex 418f3b6..e3ff8d4 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n@@ -234,11 +234,16 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\tattachContextMenu(commentViewer.getControl());\n \t\tattachContextMenu(fileViewer.getControl());\n \t\tlayout();\n+\n+\t\tRepository.addAnyRepositoryChangedListener(this);\n \t}\n \n \tprivate Runnable refschangedRunnable;\n \n \tpublic void refsChanged(final RefsChangedEvent e) {\n+\t\tif (e.getRepository() != db)\n+\t\t\treturn;\n+\n \t\tif (getControl().isDisposed())\n \t\t\treturn;\n \n@@ -480,6 +485,7 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t}\n \n \tpublic void dispose() {\n+\t\tRepository.removeAnyRepositoryChangedListener(this);\n \t\tcancelRefreshJob();\n \t\tif (popupMgr != null) {\n \t\t\tfor (final IContributionItem i : popupMgr.getItems()) {\n@@ -539,9 +545,6 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\t\trevObjectSelectionProvider.setActiveRepository(null);\n \t\tcancelRefreshJob();\n \n-\t\tif (db != null)\n-\t\t\tdb.removeRepositoryChangedListener(this);\n-\n \t\tif (graph == null)\n \t\t\treturn false;\n \n@@ -570,8 +573,6 @@ public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \t\tif (db == null)\n \t\t\treturn false;\n \n-\t\tdb.addRepositoryChangedListener(this);\n-\n \t\tfinal AnyObjectId headId;\n \t\ttry {\n \t\t\theadId = db.resolve(\"HEAD\");\n-- \n1.5.6.2.220.g44701\n"},{"id":"82880","messageId":"1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-7-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 7/9] Add a job to refresh projects when the index changes.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:49Z","receivedAt":"2008-07-10T22:40:49Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/egit/ui/Activator.java         |   83 +++++++++++++++++++-\n 1 files changed, 79 insertions(+), 4 deletions(-)\n\ndiff --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\nindex 3e02c44..39d3bc9 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n@@ -11,16 +11,19 @@ package org.spearce.egit.ui;\n import java.net.Authenticator;\n import java.net.ProxySelector;\n import java.util.HashSet;\n+import java.util.LinkedHashSet;\n import java.util.Set;\n \n import org.eclipse.core.net.proxy.IProxyService;\n import org.eclipse.core.resources.IProject;\n+import org.eclipse.core.resources.IResource;\n import org.eclipse.core.resources.ResourcesPlugin;\n import org.eclipse.core.runtime.CoreException;\n import org.eclipse.core.runtime.IProgressMonitor;\n import org.eclipse.core.runtime.IStatus;\n import org.eclipse.core.runtime.Platform;\n import org.eclipse.core.runtime.Status;\n+import org.eclipse.core.runtime.SubProgressMonitor;\n import org.eclipse.core.runtime.jobs.ISchedulingRule;\n import org.eclipse.core.runtime.jobs.Job;\n import org.eclipse.jsch.core.IJSchService;\n@@ -30,7 +33,10 @@ import org.eclipse.ui.themes.ITheme;\n import org.osgi.framework.BundleContext;\n import org.osgi.framework.ServiceReference;\n import org.spearce.egit.core.project.RepositoryMapping;\n+import org.spearce.jgit.lib.IndexChangedEvent;\n+import org.spearce.jgit.lib.RefsChangedEvent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryListener;\n import org.spearce.jgit.transport.SshSessionFactory;\n \n /**\n@@ -137,6 +143,7 @@ public class Activator extends AbstractUIPlugin {\n \n \tprivate boolean traceVerbose;\n \tprivate RCS rcs;\n+\tprivate RIRefresh refreshJob;\n \n \t/**\n \t * Constructor for the egit ui plugin singleton\n@@ -151,6 +158,70 @@ public class Activator extends AbstractUIPlugin {\n \t\tsetupSSH(context);\n \t\tsetupProxy(context);\n \t\tsetupRepoChangeScanner();\n+\t\tsetupRepoIndexRefresh();\n+\t}\n+\n+\tprivate void setupRepoIndexRefresh() {\n+\t\trefreshJob = new RIRefresh();\n+\t\tRepository.addAnyRepositoryChangedListener(refreshJob);\n+\t}\n+\n+\tstatic class RIRefresh extends Job implements RepositoryListener {\n+\n+\t\tRIRefresh() {\n+\t\t\tsuper(\"Git index refresh Job\");\n+\t\t}\n+\n+\t\tprivate Set<IProject> projectsToScan = new LinkedHashSet<IProject>();\n+\n+\t\t@Override\n+\t\tprotected IStatus run(IProgressMonitor monitor) {\n+\t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n+\t\t\tmonitor.beginTask(\"Refreshing git managed projects\", projects.length);\n+\n+\t\t\twhile (projectsToScan.size() > 0) {\n+\t\t\t\tIProject p;\n+\t\t\t\tsynchronized (projectsToScan) {\n+\t\t\t\t\tif (projectsToScan.size() == 0) {\n+\t\t\t\t\t}\n+\t\t\t\t\tp = projectsToScan.iterator().next();\n+\t\t\t\t\tprojectsToScan.remove(p);\n+\t\t\t\t}\n+\t\t\t\tISchedulingRule rule = p.getWorkspace().getRuleFactory().refreshRule(p);\n+\t\t\t\ttry {\n+\t\t\t\t\tgetJobManager().beginRule(rule, monitor);\n+\t\t\t\t\tp.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 1));\n+\t\t\t\t} catch (CoreException e) {\n+\t\t\t\t\tlogError(\"Failed to refresh projects from index changes\", e);\n+\t\t\t\t\treturn new Status(IStatus.ERROR, getPluginId(), e.getMessage());\n+\t\t\t\t} finally {\n+\t\t\t\t\tgetJobManager().endRule(rule);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tmonitor.done();\n+\t\t\treturn Status.OK_STATUS;\n+\t\t}\n+\n+\t\tpublic void indexChanged(IndexChangedEvent e) {\n+\t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n+\t\t\tSet<IProject> toRefresh= new HashSet<IProject>();\n+\t\t\tfor (IProject p : projects) {\n+\t\t\t\tRepositoryMapping mapping = RepositoryMapping.getMapping(p);\n+\t\t\t\tif (mapping != null && mapping.getRepository() == e.getRepository()) {\n+\t\t\t\t\ttoRefresh.add(p);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tsynchronized (projectsToScan) {\n+\t\t\t\tprojectsToScan.addAll(toRefresh);\n+\t\t\t}\n+\t\t\tif (projectsToScan.size() > 0)\n+\t\t\t\tschedule();\n+\t\t}\n+\n+\t\tpublic void refsChanged(RefsChangedEvent e) {\n+\t\t\t// Do not react here\n+\t\t}\n+\n \t}\n \n \tstatic class RCS extends Job {\n@@ -236,10 +307,14 @@ public class Activator extends AbstractUIPlugin {\n \n \tpublic void stop(final BundleContext context) throws Exception {\n \t\ttrace(\"Trying to cancel \" + rcs.getName() + \" job\");\n-\t\tif (!rcs.cancel()) {\n-\t\t\trcs.join();\n-\t\t}\n-\t\ttrace(\"rcs.getName() \" + rcs.getName() + \" cancelled ok\");\n+\t\trcs.cancel();\n+\t\ttrace(\"Trying to cancel \" + refreshJob.getName() + \" job\");\n+\t\trefreshJob.cancel();\n+\n+\t\trcs.join();\n+\t\trefreshJob.join();\n+\n+\t\ttrace(\"Jobs terminated\");\n \t\tsuper.stop(context);\n \t\tplugin = null;\n \t}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82882","messageId":"1215729651-26781-9-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 8/9] Make git dectected changes depend on the automatic refresh setting","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:50Z","receivedAt":"2008-07-10T22:40:50Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"This is the same setting that scans the workspace regularly for\nchanges in resources. We scan more often can can trigger sooner because\nwe can scan fewer files.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/egit/ui/Activator.java         |    5 +++++\n 1 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --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\nindex 39d3bc9..d8928cb 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n@@ -203,6 +203,11 @@ public class Activator extends AbstractUIPlugin {\n \t\t}\n \n \t\tpublic void indexChanged(IndexChangedEvent e) {\n+\t\t\t// Check the workspace setting \"refresh automatically\" setting first\n+\t\t\tif (!ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(\n+\t\t\t\t\tResourcesPlugin.PREF_AUTO_REFRESH))\n+\t\t\t\treturn;\n+\n \t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n \t\t\tSet<IProject> toRefresh= new HashSet<IProject>();\n \t\t\tfor (IProject p : projects) {\n-- \n1.5.6.2.220.g44701\n"},{"id":"82877","messageId":"1215729651-26781-10-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"1215729651-26781-9-git-send-email-robin.rosenberg@dewire.com","subject":"[EGIT PATCH 9/9] Attach the resource decorator to the repository change event mechanism","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-10T22:40:51Z","receivedAt":"2008-07-10T22:40:51Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../internal/decorators/GitResourceDecorator.java  |   29 +++++++++++++++++++-\n 1 files changed, 28 insertions(+), 1 deletions(-)\n\ndiff --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\nindex 0308f6a..dcb87b7 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java\n@@ -10,6 +10,8 @@\n package org.spearce.egit.ui.internal.decorators;\n \n import java.io.IOException;\n+import java.util.HashSet;\n+import java.util.Set;\n \n import org.eclipse.core.resources.IContainer;\n import org.eclipse.core.resources.IFile;\n@@ -38,7 +40,11 @@ import org.spearce.egit.ui.Activator;\n import org.spearce.egit.ui.UIIcons;\n import org.spearce.egit.ui.UIText;\n import org.spearce.jgit.lib.GitIndex;\n+import org.spearce.jgit.lib.IndexChangedEvent;\n+import org.spearce.jgit.lib.RefsChangedEvent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryChangedEvent;\n+import org.spearce.jgit.lib.RepositoryListener;\n import org.spearce.jgit.lib.RepositoryState;\n import org.spearce.jgit.lib.Tree;\n import org.spearce.jgit.lib.TreeEntry;\n@@ -61,7 +67,7 @@ public class GitResourceDecorator extends LabelProvider implements\n \n \tprivate static final RCL myrcl = new RCL();\n \n-\tstatic class RCL implements RepositoryChangeListener, Runnable {\n+\tstatic class RCL implements RepositoryChangeListener, RepositoryListener, Runnable {\n \t\tprivate boolean requested;\n \n \t\tpublic synchronized void run() {\n@@ -70,6 +76,26 @@ public class GitResourceDecorator extends LabelProvider implements\n \t\t\t\t\tGitResourceDecorator.class.getName());\n \t\t}\n \n+\t\tpublic void refsChanged(RefsChangedEvent e) {\n+\t\t\trepositoryChanged(e);\n+\t\t}\n+\n+\t\tpublic void indexChanged(IndexChangedEvent e) {\n+\t\t\trepositoryChanged(e);\n+\t\t}\n+\n+\t\tprivate void repositoryChanged(RepositoryChangedEvent e) {\n+\t\t\tSet<RepositoryMapping> ms = new HashSet<RepositoryMapping>();\n+\t\t\tfor (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {\n+\t\t\t\tRepositoryMapping mapping = RepositoryMapping.getMapping(p);\n+\t\t\t\tif (mapping != null && mapping.getRepository() == e.getRepository())\n+\t\t\t\t\tms.add(mapping);\n+\t\t\t}\n+\t\t\tfor (RepositoryMapping m : ms) {\n+\t\t\t\trepositoryChanged(m);\n+\t\t\t}\n+\t\t}\n+\n \t\tpublic void repositoryChanged(final RepositoryMapping which) {\n \t\t\ttry {\n \t\t\t\twhich.getContainer().accept(new IResourceVisitor() {\n@@ -139,6 +165,7 @@ public class GitResourceDecorator extends LabelProvider implements\n \tstatic ResCL myrescl = new ResCL();\n \n \tstatic {\n+\t\tRepository.addAnyRepositoryChangedListener(myrcl);\n \t\tGitProjectData.addRepositoryChangeListener(myrcl);\n \t\tResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl,\n \t\t\t\tIResourceChangeEvent.POST_CHANGE);\n-- \n1.5.6.2.220.g44701\n"},{"id":"82916","messageId":"20080711042251.GB32633@spearce.org","threadId":"14400","inReplyTo":"1215729651-26781-2-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [EGIT PATCH 1/9] Create a listener structure for changes to refs and index","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-07-11T04:22:51Z","receivedAt":"2008-07-11T04:22:51Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n> This version does not tell you which refs have changed, nor\n> what changes have happened. There is not scanning for externally\n> initiated changes either, though such changes can be found when\n> a JGit client wants to read index or refs information.\n\n> diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n> index 9e3e020..4be33b8 100644\n> --- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n> +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n> @@ -81,6 +81,10 @@ class RefDatabase {\n>  \n>  \tprivate long packedRefsLength;\n>  \n> +\tlong lastRefModification;\n> +\n> +\tlong lastNotifiedRefModification;\n> +\n>  \tRefDatabase(final Repository r) {\n>  \t\tdb = r;\n>  \t\tgitDir = db.getDirectory();\n> @@ -155,6 +161,12 @@ class RefDatabase {\n>  \t\t}\n>  \t\tif (!lck.commit())\n>  \t\t\tthrow new ObjectWritingException(\"Unable to write \" + name);\n> +\t\tsetModified();\n> +\t\tdb.fireRefsMaybeChanged();\n> +\t}\n> +\n> +\tvoid setModified() {\n> +\t\tlastRefModification = System.currentTimeMillis();\n>  \t}\n\nWe don't ever actually need the time here as a time, do we?\nWouldn't a simple counter work just as well, but ensure we don't\nlose notifications that occur more frequently then the resolution\nof currentTimeMillis()?\n\n-- \nShawn.\n"},{"id":"82917","messageId":"20080711042822.GC32633@spearce.org","threadId":"14400","inReplyTo":"1215729651-26781-5-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [EGIT PATCH 4/9] Add a method to listen to changes in any repository","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-07-11T04:28:22Z","receivedAt":"2008-07-11T04:28:22Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n> diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n> index 6f78652..dfa3045 100644\n> --- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n> +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n> @@ -95,6 +95,7 @@ public class Repository {\n>  \tprivate GitIndex index;\n>  \n>  \tprivate List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe\n> +\tstatic private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe\n...\n>  \tvoid fireRefsMaybeChanged() {\n>  \t\tif (refs.lastRefModification != refs.lastNotifiedRefModification) {\n>  \t\t\trefs.lastNotifiedRefModification = refs.lastRefModification;\n>  \t\t\tfinal RefsChangedEvent event = new RefsChangedEvent(this);\n> -\t\t\tfor (final RepositoryListener l :\n> -\t\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n> +\t\t\tList<RepositoryListener> all = new ArrayList<RepositoryListener>(\n> +\t\t\t\t\tlisteners);\n> +\t\t\tall.addAll(allListeners);\n> +\t\t\tfor (final RepositoryListener l : all) {\n>  \t\t\t\tl.refsChanged(event);\n\nI don't think this pattern is thread-safe like you think it is.\nAdding (or removing) an allListener while you are trying\nto copy the allListener collection for delivery can cause a\nConcurrentModificationException.\n\nThe preimage here is not even correct because toArray locks the\nvector and will grow the input array if it was too small, but it\nnulls out the later entries if the array was too large.  Thus you\ncan NPE inside of the fire loop.\n\nThe only safe way to do this is to lock the collection while you copy\nit into an array or list, then later iterate that to do the delivery.\n\nBoth of the fire implemenations in this patch have this issue.  :-|\n\n-- \nShawn.\n"},{"id":"82918","messageId":"20080711043317.GD32633@spearce.org","threadId":"14400","inReplyTo":"1215729651-26781-8-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [EGIT PATCH 7/9] Add a job to refresh projects when the index changes.","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-07-11T04:33:17Z","receivedAt":"2008-07-11T04:33:17Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n> +\n> +\tstatic class RIRefresh extends Job implements RepositoryListener {\n...\n> +\t\t\twhile (projectsToScan.size() > 0) {\n> +\t\t\t\tIProject p;\n> +\t\t\t\tsynchronized (projectsToScan) {\n> +\t\t\t\t\tif (projectsToScan.size() == 0) {\n> +\t\t\t\t\t}\n> +\t\t\t\t\tp = projectsToScan.iterator().next();\n\nThis is going to throw some sort of no such element exception when\nyou call next when projectsToScan is empty.  You are missing a\nbreak inside of the if test that is meant to guard against this.\n\n> +\t\t\t\t\tprojectsToScan.remove(p);\n> +\t\t\t\t}\n\nYou have gone to the trouble of allocating the Iterator to obtain\nthe next item, you should also just use it to do the deletion of\nthe next item.  Its quicker to let the Iterator do the remove as\nit already has the pointer to the proper element position.\n\n-- \nShawn.\n"},{"id":"82920","messageId":"20080711052602.GA822@spearce.org","threadId":"14400","inReplyTo":"1215729651-26781-1-git-send-email-robin.rosenberg@dewire.com","subject":"Re: [EGIT PATCH 0/9] Repository change listeners","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-07-11T05:26:02Z","receivedAt":"2008-07-11T05:26:02Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n> We want to make the UI react on changes to the repo, but the parts\n> of the code that makes the changes doesn't know who wants to react\n> on them. This adds a publish-subcribe mechanism, including detection\n> of externally made changes, e.g. by C Git.\n\nAside from the few patches I commented on, I like the overall series\n(not just this one, but the others you sent today as well).  Its a\nwelcome improvement.\n \n-- \nShawn.\n"},{"id":"82939","messageId":"1215768441-599-1-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"20080711042251.GB32633@spearce.org","subject":"[PATCH] Create a listener structure for changes to refs and index","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-11T09:27:21Z","receivedAt":"2008-07-11T09:27:21Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"This version does not tell you which refs have changed, nor\nwhat changes have happened. There is not scanning for externally\ninitiated changes either, though such changes can be found when\na JGit client wants to read index or refs information.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../egit/ui/internal/history/GitHistoryPage.java   |   31 +++++++++-\n .../src/org/spearce/jgit/lib/GitIndex.java         |    3 +\n .../org/spearce/jgit/lib/IndexChangedEvent.java    |   55 +++++++++++++++++\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   19 ++++++\n .../src/org/spearce/jgit/lib/RefsChangedEvent.java |   55 +++++++++++++++++\n .../src/org/spearce/jgit/lib/Repository.java       |   41 +++++++++++++\n .../org/spearce/jgit/lib/RepositoryAdapter.java    |   54 ++++++++++++++++\n .../spearce/jgit/lib/RepositoryChangedEvent.java   |   64 ++++++++++++++++++++\n .../org/spearce/jgit/lib/RepositoryListener.java   |   63 +++++++++++++++++++\n 9 files changed, 384 insertions(+), 1 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\n\ndiff --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\nindex 6b55185..7e2f726 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/history/GitHistoryPage.java\n@@ -61,7 +61,10 @@ import org.spearce.egit.ui.UIIcons;\n import org.spearce.egit.ui.UIPreferences;\n import org.spearce.egit.ui.UIText;\n import org.spearce.jgit.lib.AnyObjectId;\n+import org.spearce.jgit.lib.IndexChangedEvent;\n+import org.spearce.jgit.lib.RefsChangedEvent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryListener;\n import org.spearce.jgit.revplot.PlotCommit;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevFlag;\n@@ -73,7 +76,7 @@ import org.spearce.jgit.treewalk.filter.PathFilterGroup;\n import org.spearce.jgit.treewalk.filter.TreeFilter;\n \n /** Graphical commit history viewer. */\n-public class GitHistoryPage extends HistoryPage {\n+public class GitHistoryPage extends HistoryPage implements RepositoryListener {\n \tprivate static final String PREF_COMMENT_WRAP = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP;\n \n \tprivate static final String PREF_COMMENT_FILL = UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL;\n@@ -230,6 +233,32 @@ public class GitHistoryPage extends HistoryPage {\n \t\tlayout();\n \t}\n \n+\tprivate Runnable refschangedRunnable;\n+\n+\tpublic void refsChanged(final RefsChangedEvent e) {\n+\t\tif (getControl().isDisposed())\n+\t\t\treturn;\n+\n+\t\tsynchronized (this) {\n+\t\t\tif (refschangedRunnable == null) {\n+\t\t\t\trefschangedRunnable = new Runnable() {\n+\t\t\t\t\tpublic void run() {\n+\t\t\t\t\t\tif (!getControl().isDisposed()) {\n+\t\t\t\t\t\t\tActivator.trace(\"Executing async repository changed event\");\n+\t\t\t\t\t\t\trefschangedRunnable = null;\n+\t\t\t\t\t\t\tinputSet();\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t};\n+\t\t\t\tgetControl().getDisplay().asyncExec(refschangedRunnable);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tpublic void indexChanged(final IndexChangedEvent e) {\n+\t\t// We do not use index information here now\n+\t}\n+\n \tprivate void finishContextMenu() {\n \t\tpopupMgr.add(new Separator());\n \t\tpopupMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\nindex 5be404e..c7a4402 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java\n@@ -146,6 +146,7 @@ public class GitIndex {\n \tpublic void rereadIfNecessary() throws IOException {\n \t\tif (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {\n \t\t\tread();\n+\t\t\tdb.fireIndexChanged();\n \t\t}\n \t}\n \n@@ -269,6 +270,8 @@ public class GitIndex {\n \t\t\t\t\t\t\"Could not rename temporary index file to index\");\n \t\t\tchanged = false;\n \t\t\tstatDirty = false;\n+\t\t\tlastCacheTime = cacheFile.lastModified();\n+\t\t\tdb.fireIndexChanged();\n \t\t} finally {\n \t\t\tif (!lock.delete())\n \t\t\t\tthrow new IOException(\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\nnew file mode 100644\nindex 0000000..30a40d1\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/IndexChangedEvent.java\n@@ -0,0 +1,55 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about a changed Git index to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class IndexChangedEvent extends RepositoryChangedEvent {\n+\tIndexChangedEvent(final Repository repository) {\n+\t\tsuper(repository);\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"IndexChangedEvent[\" + getRepository() + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\nindex 9e3e020..ebfa7f5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n@@ -81,6 +81,12 @@ class RefDatabase {\n \n \tprivate long packedRefsLength;\n \n+\tlong lastRefModification;\n+\n+\tlong lastNotifiedRefModification;\n+\n+\tstatic int refModificationCounter;\n+\n \tRefDatabase(final Repository r) {\n \t\tdb = r;\n \t\tgitDir = db.getDirectory();\n@@ -132,6 +138,8 @@ class RefDatabase {\n \n \tvoid stored(final String name, final ObjectId id, final long time) {\n \t\tlooseRefs.put(name, new CachedRef(Ref.Storage.LOOSE, name, id, time));\n+\t\tsetModified();\n+\t\tdb.fireRefsMaybeChanged();\n \t}\n \n \t/**\n@@ -155,6 +163,12 @@ class RefDatabase {\n \t\t}\n \t\tif (!lck.commit())\n \t\t\tthrow new ObjectWritingException(\"Unable to write \" + name);\n+\t\tsetModified();\n+\t\tdb.fireRefsMaybeChanged();\n+\t}\n+\n+\tvoid setModified() {\n+\t\tlastRefModification = refModificationCounter++;\n \t}\n \n \tRef readRef(final String partialName) throws IOException {\n@@ -192,6 +206,7 @@ class RefDatabase {\n \t\treadPackedRefs(avail);\n \t\treadLooseRefs(avail, REFS_SLASH, refsDir);\n \t\treadOneLooseRef(avail, Constants.HEAD, new File(gitDir, Constants.HEAD));\n+\t\tdb.fireRefsMaybeChanged();\n \t\treturn avail;\n \t}\n \n@@ -321,6 +336,8 @@ class RefDatabase {\n \t\t\treturn r != null ? r : new Ref(Ref.Storage.LOOSE, target, null);\n \t\t}\n \n+\t\tsetModified();\n+\n \t\tfinal ObjectId id;\n \t\ttry {\n \t\t\tid = ObjectId.fromString(line);\n@@ -378,6 +395,7 @@ class RefDatabase {\n \t\t\tpackedRefsLastModified = currTime;\n \t\t\tpackedRefsLength = currLen;\n \t\t\tpackedRefs = newPackedRefs;\n+\t\t\tsetModified();\n \t\t} catch (FileNotFoundException noPackedRefs) {\n \t\t\t// Ignore it and leave the new map empty.\n \t\t\t//\n@@ -414,4 +432,5 @@ class RefDatabase {\n \t\t\tlastModified = mtime;\n \t\t}\n \t}\n+\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\nnew file mode 100644\nindex 0000000..c8936c7\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefsChangedEvent.java\n@@ -0,0 +1,55 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about a changed Git index to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class RefsChangedEvent extends RepositoryChangedEvent {\n+\tRefsChangedEvent(final Repository repository) {\n+\t\tsuper(repository);\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"RefsChangedEvent[\" + getRepository() + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 04d9b13..6f78652 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -49,7 +49,9 @@ import java.util.ArrayList;\n import java.util.Collection;\n import java.util.HashMap;\n import java.util.LinkedList;\n+import java.util.List;\n import java.util.Map;\n+import java.util.Vector;\n \n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n import org.spearce.jgit.errors.RevisionSyntaxException;\n@@ -92,6 +94,8 @@ public class Repository {\n \n \tprivate GitIndex index;\n \n+\tprivate List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe\n+\n \t/**\n \t * Construct a representation of a Git repository.\n \t * \n@@ -1028,4 +1032,41 @@ public class Repository {\n \tpublic File getWorkDir() {\n \t\treturn getDirectory().getParentFile();\n \t}\n+\n+\t/**\n+\t * Register a {@link RepositoryListener} which will be notified\n+\t * when ref changes are detected.\n+\t *\n+\t * @param l\n+\t */\n+\tpublic void addRepositoryChangedListener(final RepositoryListener l) {\n+\t\tlisteners.add(l);\n+\t}\n+\n+\t/**\n+\t * Remove a registered {@link RepositoryListener}\n+\t * @param l\n+\t */\n+\tpublic void removeRepositoryChangedListener(final RepositoryListener l) {\n+\t\tlisteners.remove(l);\n+\t}\n+\n+\tvoid fireRefsMaybeChanged() {\n+\t\tif (refs.lastRefModification != refs.lastNotifiedRefModification) {\n+\t\t\trefs.lastNotifiedRefModification = refs.lastRefModification;\n+\t\t\tfinal RefsChangedEvent event = new RefsChangedEvent(this);\n+\t\t\tfor (final RepositoryListener l :\n+\t\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\t\t\tl.refsChanged(event);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tvoid fireIndexChanged() {\n+\t\tfinal IndexChangedEvent event = new IndexChangedEvent(this);\n+\t\tfor (final RepositoryListener l :\n+\t\t\tlisteners.toArray(new RepositoryListener[listeners.size()])) {\n+\t\t\tl.indexChanged(event);\n+\t\t}\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\nnew file mode 100644\nindex 0000000..d1ff07d\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryAdapter.java\n@@ -0,0 +1,54 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * A default {@link RepositoryListener} that does nothing except invoke an\n+ * optional general method for any repository change.\n+ */\n+public class RepositoryAdapter implements RepositoryListener {\n+\n+\tpublic void indexChanged(final IndexChangedEvent e) {\n+\t\t// Empty\n+\t}\n+\n+\tpublic void refsChanged(final RefsChangedEvent e) {\n+\t\t// Empty\n+\t}\n+\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\nnew file mode 100644\nindex 0000000..b58df87\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryChangedEvent.java\n@@ -0,0 +1,64 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * This class passes information about changed refs to a\n+ * {@link RepositoryListener}\n+ *\n+ * Currently only a reference to the repository is passed.\n+ */\n+public class RepositoryChangedEvent {\n+\tprivate final Repository repository;\n+\n+\tRepositoryChangedEvent(final Repository repository) {\n+\t\tthis.repository = repository;\n+\t}\n+\n+\t/**\n+\t * @return the repository that was changed\n+\t */\n+\tpublic Repository getRepository() {\n+\t\treturn repository;\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn \"RepositoryChangedEvent[\" + repository + \"]\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\nnew file mode 100644\nindex 0000000..ceb14ce\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryListener.java\n@@ -0,0 +1,63 @@\n+/*\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.lib;\n+\n+/**\n+ * A RepositoryListener gets notification about changes in refs or repository.\n+ * <p>\n+ * It currently does <em>not</em> get notification about which items are\n+ * changed.\n+ */\n+public interface RepositoryListener {\n+\t/**\n+\t * Invoked when a ref changes\n+\t *\n+\t * @param e\n+\t *            information about the changes.\n+\t */\n+\tvoid refsChanged(RefsChangedEvent e);\n+\n+\t/**\n+\t * Invoked when the index changes\n+\t *\n+\t * @param e\n+\t *            information about the changes.\n+\t */\n+\tvoid indexChanged(IndexChangedEvent e);\n+\n+}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82941","messageId":"1215768745-1420-1-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"20080711043317.GD32633@spearce.org","subject":"[PATCH 7/7] Add a job to refresh projects when the index changes.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-11T09:32:25Z","receivedAt":"2008-07-11T09:32:25Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/egit/ui/Activator.java         |   85 +++++++++++++++++++-\n 1 files changed, 81 insertions(+), 4 deletions(-)\n\ndiff --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\nindex 3e02c44..89e6579 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java\n@@ -11,16 +11,20 @@ package org.spearce.egit.ui;\n import java.net.Authenticator;\n import java.net.ProxySelector;\n import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.LinkedHashSet;\n import java.util.Set;\n \n import org.eclipse.core.net.proxy.IProxyService;\n import org.eclipse.core.resources.IProject;\n+import org.eclipse.core.resources.IResource;\n import org.eclipse.core.resources.ResourcesPlugin;\n import org.eclipse.core.runtime.CoreException;\n import org.eclipse.core.runtime.IProgressMonitor;\n import org.eclipse.core.runtime.IStatus;\n import org.eclipse.core.runtime.Platform;\n import org.eclipse.core.runtime.Status;\n+import org.eclipse.core.runtime.SubProgressMonitor;\n import org.eclipse.core.runtime.jobs.ISchedulingRule;\n import org.eclipse.core.runtime.jobs.Job;\n import org.eclipse.jsch.core.IJSchService;\n@@ -30,7 +34,10 @@ import org.eclipse.ui.themes.ITheme;\n import org.osgi.framework.BundleContext;\n import org.osgi.framework.ServiceReference;\n import org.spearce.egit.core.project.RepositoryMapping;\n+import org.spearce.jgit.lib.IndexChangedEvent;\n+import org.spearce.jgit.lib.RefsChangedEvent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryListener;\n import org.spearce.jgit.transport.SshSessionFactory;\n \n /**\n@@ -137,6 +144,7 @@ public class Activator extends AbstractUIPlugin {\n \n \tprivate boolean traceVerbose;\n \tprivate RCS rcs;\n+\tprivate RIRefresh refreshJob;\n \n \t/**\n \t * Constructor for the egit ui plugin singleton\n@@ -151,6 +159,71 @@ public class Activator extends AbstractUIPlugin {\n \t\tsetupSSH(context);\n \t\tsetupProxy(context);\n \t\tsetupRepoChangeScanner();\n+\t\tsetupRepoIndexRefresh();\n+\t}\n+\n+\tprivate void setupRepoIndexRefresh() {\n+\t\trefreshJob = new RIRefresh();\n+\t\tRepository.addAnyRepositoryChangedListener(refreshJob);\n+\t}\n+\n+\tstatic class RIRefresh extends Job implements RepositoryListener {\n+\n+\t\tRIRefresh() {\n+\t\t\tsuper(\"Git index refresh Job\");\n+\t\t}\n+\n+\t\tprivate Set<IProject> projectsToScan = new LinkedHashSet<IProject>();\n+\n+\t\t@Override\n+\t\tprotected IStatus run(IProgressMonitor monitor) {\n+\t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n+\t\t\tmonitor.beginTask(\"Refreshing git managed projects\", projects.length);\n+\n+\t\t\twhile (projectsToScan.size() > 0) {\n+\t\t\t\tIProject p;\n+\t\t\t\tsynchronized (projectsToScan) {\n+\t\t\t\t\tif (projectsToScan.size() == 0)\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tIterator<IProject> i = projectsToScan.iterator();\n+\t\t\t\t\tp = i.next();\n+\t\t\t\t\ti.remove();\n+\t\t\t\t}\n+\t\t\t\tISchedulingRule rule = p.getWorkspace().getRuleFactory().refreshRule(p);\n+\t\t\t\ttry {\n+\t\t\t\t\tgetJobManager().beginRule(rule, monitor);\n+\t\t\t\t\tp.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 1));\n+\t\t\t\t} catch (CoreException e) {\n+\t\t\t\t\tlogError(\"Failed to refresh projects from index changes\", e);\n+\t\t\t\t\treturn new Status(IStatus.ERROR, getPluginId(), e.getMessage());\n+\t\t\t\t} finally {\n+\t\t\t\t\tgetJobManager().endRule(rule);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tmonitor.done();\n+\t\t\treturn Status.OK_STATUS;\n+\t\t}\n+\n+\t\tpublic void indexChanged(IndexChangedEvent e) {\n+\t\t\tIProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();\n+\t\t\tSet<IProject> toRefresh= new HashSet<IProject>();\n+\t\t\tfor (IProject p : projects) {\n+\t\t\t\tRepositoryMapping mapping = RepositoryMapping.getMapping(p);\n+\t\t\t\tif (mapping != null && mapping.getRepository() == e.getRepository()) {\n+\t\t\t\t\ttoRefresh.add(p);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tsynchronized (projectsToScan) {\n+\t\t\t\tprojectsToScan.addAll(toRefresh);\n+\t\t\t}\n+\t\t\tif (projectsToScan.size() > 0)\n+\t\t\t\tschedule();\n+\t\t}\n+\n+\t\tpublic void refsChanged(RefsChangedEvent e) {\n+\t\t\t// Do not react here\n+\t\t}\n+\n \t}\n \n \tstatic class RCS extends Job {\n@@ -236,10 +309,14 @@ public class Activator extends AbstractUIPlugin {\n \n \tpublic void stop(final BundleContext context) throws Exception {\n \t\ttrace(\"Trying to cancel \" + rcs.getName() + \" job\");\n-\t\tif (!rcs.cancel()) {\n-\t\t\trcs.join();\n-\t\t}\n-\t\ttrace(\"rcs.getName() \" + rcs.getName() + \" cancelled ok\");\n+\t\trcs.cancel();\n+\t\ttrace(\"Trying to cancel \" + refreshJob.getName() + \" job\");\n+\t\trefreshJob.cancel();\n+\n+\t\trcs.join();\n+\t\trefreshJob.join();\n+\n+\t\ttrace(\"Jobs terminated\");\n \t\tsuper.stop(context);\n \t\tplugin = null;\n \t}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82942","messageId":"1215769719-5238-1-git-send-email-robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"20080711042822.GC32633@spearce.org","subject":"[PATCH 4/4] Add a method to listen to changes in any repository","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-11T09:48:39Z","receivedAt":"2008-07-11T09:48:39Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |   39 ++++++++++++++++++--\n 1 files changed, 35 insertions(+), 4 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 52f6750..af860b3 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -95,6 +95,7 @@ public class Repository {\n \tprivate GitIndex index;\n \n \tprivate List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe\n+\tstatic private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe\n \n \t/**\n \t * Construct a representation of a Git repository.\n@@ -1051,12 +1052,36 @@ public class Repository {\n \t\tlisteners.remove(l);\n \t}\n \n+\t/**\n+\t * Register a global {@link RepositoryListener} which will be notified\n+\t * when a ref changes in any repository are detected.\n+\t *\n+\t * @param l\n+\t */\n+\tpublic static void addAnyRepositoryChangedListener(final RepositoryListener l) {\n+\t\tallListeners.add(l);\n+\t}\n+\n+\t/**\n+\t * Remove a globally registered {@link RepositoryListener}\n+\t * @param l\n+\t */\n+\tpublic static void removeAnyRepositoryChangedListener(final RepositoryListener l) {\n+\t\tallListeners.remove(l);\n+\t}\n+\n \tvoid fireRefsMaybeChanged() {\n \t\tif (refs.lastRefModification != refs.lastNotifiedRefModification) {\n \t\t\trefs.lastNotifiedRefModification = refs.lastRefModification;\n \t\t\tfinal RefsChangedEvent event = new RefsChangedEvent(this);\n-\t\t\tfor (final RepositoryListener l :\n-\t\t\t\tlisteners.toArray(new RepositoryListener[0])) {\n+\t\t\tList<RepositoryListener> all;\n+\t\t\tsynchronized (listeners) {\n+\t\t\t\tall = new ArrayList<RepositoryListener>(listeners);\n+\t\t\t}\n+\t\t\tsynchronized (allListeners) {\n+\t\t\t\tall.addAll(allListeners);\n+\t\t\t}\n+\t\t\tfor (final RepositoryListener l : all) {\n \t\t\t\tl.refsChanged(event);\n \t\t\t}\n \t\t}\n@@ -1064,8 +1089,14 @@ public class Repository {\n \n \tvoid fireIndexChanged() {\n \t\tfinal IndexChangedEvent event = new IndexChangedEvent(this);\n-\t\tfor (final RepositoryListener l :\n-\t\t\tlisteners.toArray(new RepositoryListener[0])) {\n+\t\tList<RepositoryListener> all;\n+\t\tsynchronized (listeners) {\n+\t\t\tall = new ArrayList<RepositoryListener>(listeners);\n+\t\t}\n+\t\tsynchronized (allListeners) {\n+\t\t\tall.addAll(allListeners);\n+\t\t}\n+\t\tfor (final RepositoryListener l : all) {\n \t\t\tl.indexChanged(event);\n \t\t}\n \t}\n-- \n1.5.6.2.220.g44701\n"},{"id":"82948","messageId":"487750E3.90303@op5.se","threadId":"14400","inReplyTo":"1215769719-5238-1-git-send-email-robin.rosenberg@dewire.com","subject":"jgit (was: [PATCH 4/4] Add a method...)","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2008-07-11T12:24:03Z","receivedAt":"2008-07-11T12:24:03Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Sorry for butting in, but I'd appreciate if you kept adding 'jgit'\nto the subject so I can keep filtering these patches out. The volume\non git@vger is high enough without having to follow off-shoots that\nare of no particular concern to me (although I certainly applaud the\neffort).\n\nThanks\n\nRobin Rosenberg wrote:\n[ a patch for jgit ]\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n"},{"id":"82951","messageId":"200807111424.14328.robin.rosenberg@dewire.com","threadId":"14400","inReplyTo":"487750E3.90303@op5.se","subject":"Re: jgit (was: [PATCH 4/4] Add a method...)","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-07-11T12:24:14Z","receivedAt":"2008-07-11T12:24:14Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"fredagen den 11 juli 2008 14.24.03 skrev Andreas Ericsson:\n> Sorry for butting in, but I'd appreciate if you kept adding 'jgit'\n> to the subject so I can keep filtering these patches out. The volume\n> on git@vger is high enough without having to follow off-shoots that\n> are of no particular concern to me (although I certainly applaud the\n> effort).\n\nIt was an accident. Honestly :)\n\n-- robin\n"}]}