diff src/org/tmatesoft/hg/internal/RevlogStream.java @ 607:66f1cc23b906

Refresh revlogs if a change to a file has been detected; do not force reload of the whole repository
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 07 May 2013 16:52:46 +0200
parents 5daa42067e7c
children e1b29756f901
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Tue May 07 14:16:35 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Tue May 07 16:52:46 2013 +0200
@@ -25,6 +25,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.zip.Inflater;
 
 import org.tmatesoft.hg.core.Nodeid;
@@ -70,11 +72,19 @@
 	// parents/children are analyzed.
 	private SoftReference<CachedRevision> lastRevisionRead;
 	private final ReferenceQueue<CachedRevision> lastRevisionQueue = new ReferenceQueue<CachedRevision>();
+	//
+	private final RevlogChangeMonitor changeTracker;
+	private List<Observer> observers;
+	private boolean shallDropDerivedCaches = false;
 
 	// if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
 	public RevlogStream(DataAccessProvider dap, File indexFile) {
 		this.dataAccess = dap;
 		this.indexFile = indexFile;
+		// TODO in fact, shall ask Internals for an instance (there we'll decide whether to use
+		// one monitor per multiple files or an instance per file; and let SessionContext pass
+		// alternative implementation)
+		changeTracker = new RevlogChangeMonitor(indexFile);
 	}
 
 	/**
@@ -214,7 +224,6 @@
 	 * @throws HgInvalidRevisionException if revisionIndex argument doesn't represent a valid record in the revlog
 	 */
 	public int baseRevision(int revisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException {
-		initOutline();
 		revisionIndex = checkRevisionIndex(revisionIndex);
 		return getBaseRevision(revisionIndex);
 	}
@@ -354,8 +363,41 @@
 			setLastRevisionRead(cr);
 		}
 	}
+	
+	public void attach(Observer listener) {
+		assert listener != null;
+		if (observers == null) {
+			observers = new ArrayList<Observer>(3);
+		}
+		observers.add(listener);
+	}
+	
+	public void detach(Observer listener) {
+		assert listener != null;
+		if (observers != null) {
+			observers.remove(listener);
+		}
+	}
+	
+	/*
+	 * Note, this method IS NOT a replacement for Observer. It has to be invoked when the validity of any
+	 * cache built using revision information is in doubt, but it provides reasonable value only till the
+	 * first initOutline() to be invoked, i.e. in [change..revlog read operation] time frame. If your code
+	 * accesses cached information without any prior explicit read operation, you shall consult this method
+	 * if next read operation would in fact bring changed content.
+	 * Observer is needed in addition to this method because any revlog read operation (e.g. Revlog#getLastRevision)
+	 * would clear shallDropDerivedCaches(), and if code relies only on this method to clear its derived caches,
+	 * it would miss the update.
+	 */
+	public boolean shallDropDerivedCaches() {
+		if (shallDropDerivedCaches) {
+			return shallDropDerivedCaches;
+		}
+		return shallDropDerivedCaches = changeTracker.hasChanged(indexFile);
+	}
 
 	void revisionAdded(int revisionIndex, Nodeid revision, int baseRevisionIndex, long revisionOffset) throws HgInvalidControlFileException {
+		shallDropDerivedCaches = true;
 		if (!outlineCached()) {
 			return;
 		}
@@ -421,10 +463,20 @@
 		return o + REVLOGV1_RECORD_SIZE * recordIndex;
 	}
 
+	// every access to index revlog goes after this method only.
 	private void initOutline() throws HgInvalidControlFileException {
+		// true to send out 'drop-your-caches' event after outline has been built
+		final boolean notifyReload;
 		if (outlineCached()) {
-			return;
+			if (!changeTracker.hasChanged(indexFile)) {
+				return;
+			}
+			notifyReload = true;
+		} else {
+			// no cached outline - inital read, do not send any reload/invalidate notifications
+			notifyReload = false;
 		}
+		changeTracker.touch(indexFile);
 		DataAccess da = getIndexStream(false);
 		try {
 			if (da.isEmpty()) {
@@ -483,6 +535,12 @@
 			throw new HgInvalidControlFileException("Failed to analyze revlog index", ex, indexFile);
 		} finally {
 			da.done();
+			if (notifyReload && observers != null) {
+				for (Observer l : observers) {
+					l.reloaded(this);
+				}
+				shallDropDerivedCaches = false;
+			}
 		}
 	}
 	
@@ -777,4 +835,8 @@
 		void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data);
 	}
 
+	public interface Observer {
+		// notify observer of invalidate/reload event in the stream
+		public void reloaded(RevlogStream src);
+	}
 }