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