Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/RevlogStream.java @ 608:e1b29756f901
Clean, organize and resolve some TODOs and FIXMEs: minor refactorings and comments
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> | 
|---|---|
| date | Tue, 07 May 2013 21:27:51 +0200 | 
| parents | 66f1cc23b906 | 
| children | 65c01508f002 | 
   comparison
  equal
  deleted
  inserted
  replaced
| 607:66f1cc23b906 | 608:e1b29756f901 | 
|---|---|
| 48 * @author Artem Tikhomirov | 48 * @author Artem Tikhomirov | 
| 49 * @author TMate Software Ltd. | 49 * @author TMate Software Ltd. | 
| 50 */ | 50 */ | 
| 51 public class RevlogStream { | 51 public class RevlogStream { | 
| 52 | 52 | 
| 53 static final int INLINEDATA = 1 << 16; | |
| 54 | |
| 53 /* | 55 /* | 
| 54 * makes sense for index with inline data only - actual offset of the record in the .i file (record entry + revision * record size)) | 56 * makes sense for index with inline data only - actual offset of the record in the .i file (record entry + revision * record size)) | 
| 55 * | 57 * | 
| 56 * long[] in fact (there are 8-bytes field in the revlog) | 58 * long[] in fact (there are 8-bytes field in the revlog) | 
| 57 * However, (a) DataAccess currently doesn't operate with long seek/length | 59 * However, (a) DataAccess currently doesn't operate with long seek/length | 
| 61 private int[] indexRecordOffset; | 63 private int[] indexRecordOffset; | 
| 62 private int[] baseRevisions; | 64 private int[] baseRevisions; | 
| 63 private boolean inline = false; | 65 private boolean inline = false; | 
| 64 private final File indexFile; | 66 private final File indexFile; | 
| 65 private File dataFile; | 67 private File dataFile; | 
| 66 private final DataAccessProvider dataAccess; | 68 private final Internals repo; | 
| 67 // keeps last complete revision we've read. Note, this cached revision doesn't help | 69 // keeps last complete revision we've read. Note, this cached revision doesn't help | 
| 68 // for subsequent #iterate() calls with the same revision (Inspector needs more data than | 70 // for subsequent #iterate() calls with the same revision (Inspector needs more data than | 
| 69 // we currently cache here, perhaps, we shall cache everything it wants to cover same | 71 // we currently cache here, perhaps, we shall cache everything it wants to cover same | 
| 70 // revision case as well). Now this helps when second #iterate() call is for a revision greater | 72 // revision case as well). Now this helps when second #iterate() call is for a revision greater | 
| 71 // than one from the first call, and both revisions got same base rev. It's often the case when | 73 // than one from the first call, and both revisions got same base rev. It's often the case when | 
| 75 // | 77 // | 
| 76 private final RevlogChangeMonitor changeTracker; | 78 private final RevlogChangeMonitor changeTracker; | 
| 77 private List<Observer> observers; | 79 private List<Observer> observers; | 
| 78 private boolean shallDropDerivedCaches = false; | 80 private boolean shallDropDerivedCaches = false; | 
| 79 | 81 | 
| 80 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP. | 82 public RevlogStream(Internals hgRepo, File indexFile) { | 
| 81 public RevlogStream(DataAccessProvider dap, File indexFile) { | 83 repo = hgRepo; | 
| 82 this.dataAccess = dap; | |
| 83 this.indexFile = indexFile; | 84 this.indexFile = indexFile; | 
| 84 // TODO in fact, shall ask Internals for an instance (there we'll decide whether to use | 85 changeTracker = repo.getRevlogTracker(indexFile); | 
| 85 // one monitor per multiple files or an instance per file; and let SessionContext pass | |
| 86 // alternative implementation) | |
| 87 changeTracker = new RevlogChangeMonitor(indexFile); | |
| 88 } | 86 } | 
| 89 | 87 | 
| 90 /** | 88 /** | 
| 91 * @param shortRead pass <code>true</code> to indicate intention to read few revisions only (as opposed to reading most of/complete revlog) | 89 * @param shortRead pass <code>true</code> to indicate intention to read few revisions only (as opposed to reading most of/complete revlog) | 
| 92 * @return never <code>null</code>, empty {@link DataAccess} if no stream is available | 90 * @return never <code>null</code>, empty {@link DataAccess} if no stream is available | 
| 93 */ | 91 */ | 
| 94 /*package*/ DataAccess getIndexStream(boolean shortRead) { | 92 /*package*/ DataAccess getIndexStream(boolean shortRead) { | 
| 95 // shortRead hint helps to avoid mmap files when only | 93 // shortRead hint helps to avoid mmap files when only | 
| 96 // few bytes are to be read (i.e. #dataLength()) | 94 // few bytes are to be read (i.e. #dataLength()) | 
| 95 DataAccessProvider dataAccess = repo.getDataAccess(); | |
| 97 return dataAccess.createReader(indexFile, shortRead); | 96 return dataAccess.createReader(indexFile, shortRead); | 
| 98 } | 97 } | 
| 99 | 98 | 
| 100 /*package*/ DataAccess getDataStream() { | 99 /*package*/ DataAccess getDataStream() { | 
| 100 DataAccessProvider dataAccess = repo.getDataAccess(); | |
| 101 return dataAccess.createReader(getDataFile(), false); | 101 return dataAccess.createReader(getDataFile(), false); | 
| 102 } | 102 } | 
| 103 | 103 | 
| 104 /*package*/ DataSerializer getIndexStreamWriter() { | 104 /*package*/ DataSerializer getIndexStreamWriter() { | 
| 105 DataAccessProvider dataAccess = repo.getDataAccess(); | |
| 105 return dataAccess.createWriter(indexFile, true); | 106 return dataAccess.createWriter(indexFile, true); | 
| 106 } | 107 } | 
| 107 | 108 | 
| 108 /*package*/ DataSerializer getDataStreamWriter() { | 109 /*package*/ DataSerializer getDataStreamWriter() { | 
| 110 DataAccessProvider dataAccess = repo.getDataAccess(); | |
| 109 return dataAccess.createWriter(getDataFile(), true); | 111 return dataAccess.createWriter(getDataFile(), true); | 
| 110 } | 112 } | 
| 111 | 113 | 
| 112 /** | 114 /** | 
| 113 * Constructs file object that corresponds to .d revlog counterpart. | 115 * Constructs file object that corresponds to .d revlog counterpart. | 
| 299 start = indexSize - 1; | 301 start = indexSize - 1; | 
| 300 } | 302 } | 
| 301 HgInternals.checkRevlogRange(start, end, indexSize-1); | 303 HgInternals.checkRevlogRange(start, end, indexSize-1); | 
| 302 // XXX may cache [start .. end] from index with a single read (pre-read) | 304 // XXX may cache [start .. end] from index with a single read (pre-read) | 
| 303 | 305 | 
| 304 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches()); | 306 ReaderN1 r = new ReaderN1(needData, inspector, repo.shallMergePatches()); | 
| 305 try { | 307 try { | 
| 306 r.start(end - start + 1, getLastRevisionRead()); | 308 r.start(end - start + 1, getLastRevisionRead()); | 
| 307 r.range(start, end); | 309 r.range(start, end); | 
| 308 } catch (IOException ex) { | 310 } catch (IOException ex) { | 
| 309 throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile); | 311 throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile); | 
| 310 } catch (HgInvalidControlFileException ex) { | |
| 311 throw ex; | |
| 312 } finally { | 312 } finally { | 
| 313 CachedRevision cr = r.finish(); | 313 CachedRevision cr = r.finish(); | 
| 314 setLastRevisionRead(cr); | 314 setLastRevisionRead(cr); | 
| 315 } | 315 } | 
| 316 } | 316 } | 
| 332 } | 332 } | 
| 333 if (sortedRevisions[sortedRevisions.length - 1] > indexSize) { | 333 if (sortedRevisions[sortedRevisions.length - 1] > indexSize) { | 
| 334 throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]); | 334 throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]); | 
| 335 } | 335 } | 
| 336 | 336 | 
| 337 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches()); | 337 ReaderN1 r = new ReaderN1(needData, inspector, repo.shallMergePatches()); | 
| 338 try { | 338 try { | 
| 339 r.start(sortedRevisions.length, getLastRevisionRead()); | 339 r.start(sortedRevisions.length, getLastRevisionRead()); | 
| 340 for (int i = 0; i < sortedRevisions.length; ) { | 340 for (int i = 0; i < sortedRevisions.length; ) { | 
| 341 int x = i; | 341 int x = i; | 
| 342 i++; | 342 i++; | 
| 352 return; | 352 return; | 
| 353 } | 353 } | 
| 354 } | 354 } | 
| 355 } catch (IOException ex) { | 355 } catch (IOException ex) { | 
| 356 final int c = sortedRevisions.length; | 356 final int c = sortedRevisions.length; | 
| 357 throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]",c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile); | 357 throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]", c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile); | 
| 358 } catch (HgInvalidControlFileException ex) { | |
| 359 // TODO post-1.0 fill HgRuntimeException with appropriate file (either index or data, depending on error source) | |
| 360 throw ex; | |
| 361 } finally { | 358 } finally { | 
| 362 CachedRevision cr = r.finish(); | 359 CachedRevision cr = r.finish(); | 
| 363 setLastRevisionRead(cr); | 360 setLastRevisionRead(cr); | 
| 364 } | 361 } | 
| 365 } | 362 } | 
| 486 inline = true; | 483 inline = true; | 
| 487 return; | 484 return; | 
| 488 } | 485 } | 
| 489 int versionField = da.readInt(); | 486 int versionField = da.readInt(); | 
| 490 da.readInt(); // just to skip next 4 bytes of offset + flags | 487 da.readInt(); // just to skip next 4 bytes of offset + flags | 
| 491 final int INLINEDATA = 1 << 16; | |
| 492 inline = (versionField & INLINEDATA) != 0; | 488 inline = (versionField & INLINEDATA) != 0; | 
| 493 IntVector resBases, resOffsets = null; | 489 IntVector resBases, resOffsets = null; | 
| 494 int entryCountGuess = Internals.ltoi(da.longLength() / REVLOGV1_RECORD_SIZE); | 490 int entryCountGuess = Internals.ltoi(da.longLength() / REVLOGV1_RECORD_SIZE); | 
| 495 if (inline) { | 491 if (inline) { | 
| 496 entryCountGuess >>>= 2; // pure guess, assume useful data takes 3/4 of total space | 492 entryCountGuess >>>= 2; // pure guess, assume useful data takes 3/4 of total space | 
| 590 private DataAccess lastUserData; | 586 private DataAccess lastUserData; | 
| 591 // | 587 // | 
| 592 // next are transient values, for range() use only | 588 // next are transient values, for range() use only | 
| 593 private final Inflater inflater = new Inflater(); | 589 private final Inflater inflater = new Inflater(); | 
| 594 // can share buffer between instances of InflaterDataAccess as I never read any two of them in parallel | 590 // can share buffer between instances of InflaterDataAccess as I never read any two of them in parallel | 
| 595 private final byte[] inflaterBuffer = new byte[10 * 1024]; // TODO consider using DAP.DEFAULT_FILE_BUFFER | 591 private final byte[] inflaterBuffer = new byte[10 * 1024]; // TODO [post-1.1] consider using DAP.DEFAULT_FILE_BUFFER | 
| 596 private final byte[] nodeidBuf = new byte[20]; | 592 private final byte[] nodeidBuf = new byte[20]; | 
| 597 // revlog record fields | 593 // revlog record fields | 
| 598 private long offset; | 594 private long offset; | 
| 599 @SuppressWarnings("unused") | 595 @SuppressWarnings("unused") | 
| 600 private int flags; | 596 private int flags; | 
| 827 } | 823 } | 
| 828 } | 824 } | 
| 829 | 825 | 
| 830 | 826 | 
| 831 public interface Inspector { | 827 public interface Inspector { | 
| 832 // XXX boolean retVal to indicate whether to continue? | 828 /** | 
| 833 // TODO specify nodeid and data length, and reuse policy (i.e. if revlog stream doesn't reuse nodeid[] for each call) | 829 * XXX boolean retVal to indicate whether to continue? | 
| 834 // implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropraite moment | 830 * | 
| 831 * Implementers shall not invoke DataAccess.done(), it's accomplished by #iterate at appropriate moment | |
| 832 * | |
| 833 * @param revisionIndex absolute index of revision in revlog being iterated | |
| 834 * @param actualLen length of the user data at this revision | |
| 835 * @param baseRevision last revision known to hold complete revision (other hold patches). | |
| 836 * if baseRevision != revisionIndex, data for this revision is a result of a sequence of patches | |
| 837 * @param linkRevision index of corresponding changeset revision | |
| 838 * @param parent1Revision index of first parent revision in this revlog, or {@link HgRepository#NO_REVISION} | |
| 839 * @param parent2Revision index of second parent revision in this revlog, or {@link HgRepository#NO_REVISION} | |
| 840 * @param nodeid 20-byte buffer, shared between invocations | |
| 841 * @param data access to revision content of actualLen size, or <code>null</code> if no data has been requested with | |
| 842 * {@link RevlogStream#iterate(int[], boolean, Inspector)} | |
| 843 */ | |
| 835 void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data); | 844 void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*20*/] nodeid, DataAccess data); | 
| 836 } | 845 } | 
| 837 | 846 | 
| 838 public interface Observer { | 847 public interface Observer { | 
| 839 // notify observer of invalidate/reload event in the stream | 848 // notify observer of invalidate/reload event in the stream | 
