comparison 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
comparison
equal deleted inserted replaced
606:5daa42067e7c 607:66f1cc23b906
23 import java.io.File; 23 import java.io.File;
24 import java.io.IOException; 24 import java.io.IOException;
25 import java.lang.ref.Reference; 25 import java.lang.ref.Reference;
26 import java.lang.ref.ReferenceQueue; 26 import java.lang.ref.ReferenceQueue;
27 import java.lang.ref.SoftReference; 27 import java.lang.ref.SoftReference;
28 import java.util.ArrayList;
29 import java.util.List;
28 import java.util.zip.Inflater; 30 import java.util.zip.Inflater;
29 31
30 import org.tmatesoft.hg.core.Nodeid; 32 import org.tmatesoft.hg.core.Nodeid;
31 import org.tmatesoft.hg.repo.HgInternals; 33 import org.tmatesoft.hg.repo.HgInternals;
32 import org.tmatesoft.hg.repo.HgInvalidControlFileException; 34 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
68 // revision case as well). Now this helps when second #iterate() call is for a revision greater 70 // revision case as well). Now this helps when second #iterate() call is for a revision greater
69 // than one from the first call, and both revisions got same base rev. It's often the case when 71 // than one from the first call, and both revisions got same base rev. It's often the case when
70 // parents/children are analyzed. 72 // parents/children are analyzed.
71 private SoftReference<CachedRevision> lastRevisionRead; 73 private SoftReference<CachedRevision> lastRevisionRead;
72 private final ReferenceQueue<CachedRevision> lastRevisionQueue = new ReferenceQueue<CachedRevision>(); 74 private final ReferenceQueue<CachedRevision> lastRevisionQueue = new ReferenceQueue<CachedRevision>();
75 //
76 private final RevlogChangeMonitor changeTracker;
77 private List<Observer> observers;
78 private boolean shallDropDerivedCaches = false;
73 79
74 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP. 80 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
75 public RevlogStream(DataAccessProvider dap, File indexFile) { 81 public RevlogStream(DataAccessProvider dap, File indexFile) {
76 this.dataAccess = dap; 82 this.dataAccess = dap;
77 this.indexFile = indexFile; 83 this.indexFile = indexFile;
84 // TODO in fact, shall ask Internals for an instance (there we'll decide whether to use
85 // one monitor per multiple files or an instance per file; and let SessionContext pass
86 // alternative implementation)
87 changeTracker = new RevlogChangeMonitor(indexFile);
78 } 88 }
79 89
80 /** 90 /**
81 * @param shortRead pass <code>true</code> to indicate intention to read few revisions only (as opposed to reading most of/complete revlog) 91 * @param shortRead pass <code>true</code> to indicate intention to read few revisions only (as opposed to reading most of/complete revlog)
82 * @return never <code>null</code>, empty {@link DataAccess} if no stream is available 92 * @return never <code>null</code>, empty {@link DataAccess} if no stream is available
212 * 222 *
213 * @throws HgInvalidControlFileException if attempt to read index file failed 223 * @throws HgInvalidControlFileException if attempt to read index file failed
214 * @throws HgInvalidRevisionException if revisionIndex argument doesn't represent a valid record in the revlog 224 * @throws HgInvalidRevisionException if revisionIndex argument doesn't represent a valid record in the revlog
215 */ 225 */
216 public int baseRevision(int revisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { 226 public int baseRevision(int revisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException {
217 initOutline();
218 revisionIndex = checkRevisionIndex(revisionIndex); 227 revisionIndex = checkRevisionIndex(revisionIndex);
219 return getBaseRevision(revisionIndex); 228 return getBaseRevision(revisionIndex);
220 } 229 }
221 230
222 // Perhaps, RevlogStream should be limited to use of plain int revisions for access, 231 // Perhaps, RevlogStream should be limited to use of plain int revisions for access,
352 } finally { 361 } finally {
353 CachedRevision cr = r.finish(); 362 CachedRevision cr = r.finish();
354 setLastRevisionRead(cr); 363 setLastRevisionRead(cr);
355 } 364 }
356 } 365 }
366
367 public void attach(Observer listener) {
368 assert listener != null;
369 if (observers == null) {
370 observers = new ArrayList<Observer>(3);
371 }
372 observers.add(listener);
373 }
374
375 public void detach(Observer listener) {
376 assert listener != null;
377 if (observers != null) {
378 observers.remove(listener);
379 }
380 }
381
382 /*
383 * Note, this method IS NOT a replacement for Observer. It has to be invoked when the validity of any
384 * cache built using revision information is in doubt, but it provides reasonable value only till the
385 * first initOutline() to be invoked, i.e. in [change..revlog read operation] time frame. If your code
386 * accesses cached information without any prior explicit read operation, you shall consult this method
387 * if next read operation would in fact bring changed content.
388 * Observer is needed in addition to this method because any revlog read operation (e.g. Revlog#getLastRevision)
389 * would clear shallDropDerivedCaches(), and if code relies only on this method to clear its derived caches,
390 * it would miss the update.
391 */
392 public boolean shallDropDerivedCaches() {
393 if (shallDropDerivedCaches) {
394 return shallDropDerivedCaches;
395 }
396 return shallDropDerivedCaches = changeTracker.hasChanged(indexFile);
397 }
357 398
358 void revisionAdded(int revisionIndex, Nodeid revision, int baseRevisionIndex, long revisionOffset) throws HgInvalidControlFileException { 399 void revisionAdded(int revisionIndex, Nodeid revision, int baseRevisionIndex, long revisionOffset) throws HgInvalidControlFileException {
400 shallDropDerivedCaches = true;
359 if (!outlineCached()) { 401 if (!outlineCached()) {
360 return; 402 return;
361 } 403 }
362 if (baseRevisions.length != revisionIndex) { 404 if (baseRevisions.length != revisionIndex) {
363 throw new HgInvalidControlFileException(String.format("New entry's index shall be %d, not %d", baseRevisions.length, revisionIndex), null, indexFile); 405 throw new HgInvalidControlFileException(String.format("New entry's index shall be %d, not %d", baseRevisions.length, revisionIndex), null, indexFile);
419 throw new HgInvalidStateException("Data too big, offset didn't fit to sizeof(int)"); 461 throw new HgInvalidStateException("Data too big, offset didn't fit to sizeof(int)");
420 } 462 }
421 return o + REVLOGV1_RECORD_SIZE * recordIndex; 463 return o + REVLOGV1_RECORD_SIZE * recordIndex;
422 } 464 }
423 465
466 // every access to index revlog goes after this method only.
424 private void initOutline() throws HgInvalidControlFileException { 467 private void initOutline() throws HgInvalidControlFileException {
468 // true to send out 'drop-your-caches' event after outline has been built
469 final boolean notifyReload;
425 if (outlineCached()) { 470 if (outlineCached()) {
426 return; 471 if (!changeTracker.hasChanged(indexFile)) {
427 } 472 return;
473 }
474 notifyReload = true;
475 } else {
476 // no cached outline - inital read, do not send any reload/invalidate notifications
477 notifyReload = false;
478 }
479 changeTracker.touch(indexFile);
428 DataAccess da = getIndexStream(false); 480 DataAccess da = getIndexStream(false);
429 try { 481 try {
430 if (da.isEmpty()) { 482 if (da.isEmpty()) {
431 // do not fail with exception if stream is empty, it's likely intentional 483 // do not fail with exception if stream is empty, it's likely intentional
432 baseRevisions = new int[0]; 484 baseRevisions = new int[0];
481 } 533 }
482 } catch (IOException ex) { 534 } catch (IOException ex) {
483 throw new HgInvalidControlFileException("Failed to analyze revlog index", ex, indexFile); 535 throw new HgInvalidControlFileException("Failed to analyze revlog index", ex, indexFile);
484 } finally { 536 } finally {
485 da.done(); 537 da.done();
538 if (notifyReload && observers != null) {
539 for (Observer l : observers) {
540 l.reloaded(this);
541 }
542 shallDropDerivedCaches = false;
543 }
486 } 544 }
487 } 545 }
488 546
489 private CachedRevision getLastRevisionRead() { 547 private CachedRevision getLastRevisionRead() {
490 return lastRevisionRead == null ? null : lastRevisionRead.get(); 548 return lastRevisionRead == null ? null : lastRevisionRead.get();
775 // TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call) 833 // TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call)
776 // implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment 834 // implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment
777 void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data); 835 void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data);
778 } 836 }
779 837
838 public interface Observer {
839 // notify observer of invalidate/reload event in the stream
840 public void reloaded(RevlogStream src);
841 }
780 } 842 }