Mercurial > jhg
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 } |
