Mercurial > jhg
comparison src/com/tmate/hgkit/ll/RevlogStream.java @ 2:08db726a0fb7
Shaping out low-level Hg structures
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Sun, 19 Dec 2010 05:41:31 +0100 |
| parents | dbd663faec1f |
| children | 24bb4f365164 |
comparison
equal
deleted
inserted
replaced
| 1:a3576694a4d1 | 2:08db726a0fb7 |
|---|---|
| 2 * Copyright (c) 2010 Artem Tikhomirov | 2 * Copyright (c) 2010 Artem Tikhomirov |
| 3 */ | 3 */ |
| 4 package com.tmate.hgkit.ll; | 4 package com.tmate.hgkit.ll; |
| 5 | 5 |
| 6 import java.io.DataInput; | 6 import java.io.DataInput; |
| 7 import java.io.EOFException; | |
| 8 import java.io.IOException; | |
| 9 import java.util.ArrayList; | |
| 10 import java.util.Collections; | |
| 11 import java.util.List; | |
| 12 import java.util.zip.Inflater; | |
| 7 | 13 |
| 8 /** | 14 /** |
| 9 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), | 15 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), |
| 10 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? | 16 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? |
| 11 * @author artem | 17 * @author artem |
| 12 * @see http://mercurial.selenic.com/wiki/Revlog | 18 * @see http://mercurial.selenic.com/wiki/Revlog |
| 13 * @see http://mercurial.selenic.com/wiki/RevlogNG | 19 * @see http://mercurial.selenic.com/wiki/RevlogNG |
| 14 */ | 20 */ |
| 15 public class RevlogStream { | 21 public class RevlogStream { |
| 16 | 22 |
| 23 private List<IndexEntry> index; // indexed access highly needed | |
| 24 private boolean inline = false; | |
| 25 | |
| 17 private void detectVersion() { | 26 private void detectVersion() { |
| 18 | 27 |
| 19 } | 28 } |
| 20 | 29 |
| 21 /*package*/ DataInput getIndexStream() { | 30 /*package*/ DataInput getIndexStream() { |
| 25 | 34 |
| 26 /*package*/ DataInput getDataStream() { | 35 /*package*/ DataInput getDataStream() { |
| 27 // TODO Auto-generated method stub | 36 // TODO Auto-generated method stub |
| 28 return null; | 37 return null; |
| 29 } | 38 } |
| 39 public int revisionCount() { | |
| 40 initOutline(); | |
| 41 return index.size(); | |
| 42 } | |
| 43 | |
| 44 // should be possible to use TIP, ALL | |
| 45 public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) { | |
| 46 initOutline(); | |
| 47 final int indexSize = index.size(); | |
| 48 if (start < 0 || start >= indexSize) { | |
| 49 throw new IllegalArgumentException("Bad left range boundary " + start); | |
| 50 } | |
| 51 if (end < start || end >= indexSize) { | |
| 52 throw new IllegalArgumentException("Bad right range boundary " + end); | |
| 53 } | |
| 54 // XXX may cache [start .. end] from index with a single read (pre-read) | |
| 55 | |
| 56 DataInput diIndex = null, diData = null; | |
| 57 diIndex = getIndexStream(); | |
| 58 if (needData) { | |
| 59 diData = getDataStream(); | |
| 60 } | |
| 61 try { | |
| 62 diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64); | |
| 63 for (int i = start; i <= end && i < indexSize; i++ ) { | |
| 64 IndexEntry ie = index.get(i); | |
| 65 long l = diIndex.readLong(); | |
| 66 long offset = l >>> 16; | |
| 67 int flags = (int) (l & 0X0FFFF); | |
| 68 int compressedLen = diIndex.readInt(); | |
| 69 int actualLen = diIndex.readInt(); | |
| 70 int baseRevision = diIndex.readInt(); | |
| 71 int linkRevision = diIndex.readInt(); | |
| 72 int parent1Revision = diIndex.readInt(); | |
| 73 int parent2Revision = diIndex.readInt(); | |
| 74 byte[] buf = new byte[32]; | |
| 75 // XXX Hg keeps 12 last bytes empty, we move them into front here | |
| 76 diIndex.readFully(buf, 12, 20); | |
| 77 diIndex.skipBytes(12); | |
| 78 byte[] data = null; | |
| 79 if (needData) { | |
| 80 byte[] dataBuf = new byte[compressedLen]; | |
| 81 if (inline) { | |
| 82 diIndex.readFully(dataBuf); | |
| 83 } else { | |
| 84 diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! | |
| 85 diData.readFully(dataBuf); | |
| 86 } | |
| 87 if (dataBuf[0] == 0x78 /* 'x' */) { | |
| 88 Inflater zlib = new Inflater(); | |
| 89 zlib.setInput(dataBuf, 0, compressedLen); | |
| 90 byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead | |
| 91 int resultLen = zlib.inflate(result); | |
| 92 zlib.end(); | |
| 93 data = new byte[resultLen]; | |
| 94 System.arraycopy(result, 0, data, 0, resultLen); | |
| 95 } else if (dataBuf[0] == 0x75 /* 'u' */) { | |
| 96 data = new byte[dataBuf.length - 1]; | |
| 97 System.arraycopy(dataBuf, 1, data, 0, data.length); | |
| 98 } else { | |
| 99 // XXX Python impl in fact throws exception when there's not 'x', 'u' or '0' | |
| 100 // but I don't see reason not to just return data as is | |
| 101 data = dataBuf; | |
| 102 } | |
| 103 // FIXME if patch data (based on baseRevision), then apply patches to get true content | |
| 104 } | |
| 105 inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data); | |
| 106 } | |
| 107 } catch (EOFException ex) { | |
| 108 // should not happen as long as we read inside known boundaries | |
| 109 throw new IllegalStateException(ex); | |
| 110 } catch (IOException ex) { | |
| 111 FIXME | |
| 112 } | |
| 113 } | |
| 30 | 114 |
| 115 private void initOutline() { | |
| 116 if (index != null && !index.isEmpty()) { | |
| 117 return; | |
| 118 } | |
| 119 ArrayList<IndexEntry> res = new ArrayList<IndexEntry>(); | |
| 120 DataInput di = getIndexStream(); | |
| 121 try { | |
| 122 int versionField = di.readInt(); | |
| 123 // di.undreadInt(); | |
| 124 final int INLINEDATA = 1 << 16; | |
| 125 inline = (versionField & INLINEDATA) != 0; | |
| 126 long offset = 0; // first offset is always 0, thus Hg uses it for other purposes | |
| 127 while(true) { // EOFExcepiton should get us outta here. FIXME Out inputstream should has explicit no-more-data indicator | |
| 128 int compressedLen = di.readInt(); | |
| 129 // 8+4 = 12 bytes total read | |
| 130 // int actualLen = di.readInt(); | |
| 131 // int baseRevision = di.readInt(); | |
| 132 // int linkRevision = di.readInt(); | |
| 133 // int parent1Revision = di.readInt(); | |
| 134 // int parent2Revision = di.readInt(); | |
| 135 // byte[] nodeid = new byte[32]; | |
| 136 res.add(new IndexEntry(offset, compressedLen)); | |
| 137 if (inline) { | |
| 138 di.skipBytes(6*4 + 32 + compressedLen); // Check: 56 (skip) + 12 (read) = 64 (total RevlogNG record size) | |
| 139 } else { | |
| 140 di.skipBytes(6*4 + 32); | |
| 141 } | |
| 142 long l = di.readLong(); | |
| 143 offset = l >>> 16; | |
| 144 } | |
| 145 } catch (EOFException ex) { | |
| 146 // fine, done then | |
| 147 index = res; | |
| 148 } catch (IOException ex) { | |
| 149 ex.printStackTrace(); | |
| 150 // too bad, no outline then | |
| 151 index = Collections.emptyList(); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 | |
| 156 // perhaps, package-local or protected, if anyone else from low-level needs them | |
| 157 private static class IndexEntry { | |
| 158 public final long offset; | |
| 159 public final int length; // data past fixed record (need to decide whether including header size or not), and whether length is of compressed data or not | |
| 160 | |
| 161 public IndexEntry(long o, int l) { | |
| 162 offset = o; | |
| 163 length = l; | |
| 164 } | |
| 165 } | |
| 31 } | 166 } |
