Mercurial > hg4j
comparison src/org/tmatesoft/hg/internal/RevlogStreamWriter.java @ 538:dd4f6311af52
Commit: first working version
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Tue, 05 Feb 2013 22:30:21 +0100 |
parents | 243202f1bda5 |
children | 9edfd5a223b8 |
comparison
equal
deleted
inserted
replaced
537:5a455624be4f | 538:dd4f6311af52 |
---|---|
34 * @author Artem Tikhomirov | 34 * @author Artem Tikhomirov |
35 * @author TMate Software Ltd. | 35 * @author TMate Software Ltd. |
36 */ | 36 */ |
37 public class RevlogStreamWriter { | 37 public class RevlogStreamWriter { |
38 | 38 |
39 | |
40 /*XXX public because HgCloneCommand uses it*/ | |
41 public static class HeaderWriter implements DataSerializer.DataSource { | |
42 private final ByteBuffer header; | |
43 private final boolean isInline; | |
44 private long offset; | |
45 private int length, compressedLength; | |
46 private int baseRev, linkRev, p1, p2; | |
47 private byte[] nodeid; | |
48 | |
49 public HeaderWriter(boolean inline) { | |
50 isInline = inline; | |
51 header = ByteBuffer.allocate(REVLOGV1_RECORD_SIZE); | |
52 } | |
53 | |
54 public HeaderWriter offset(long offset) { | |
55 this.offset = offset; | |
56 return this; | |
57 } | |
58 | |
59 public int baseRevision() { | |
60 return baseRev; | |
61 } | |
62 | |
63 public HeaderWriter baseRevision(int baseRevision) { | |
64 this.baseRev = baseRevision; | |
65 return this; | |
66 } | |
67 | |
68 public HeaderWriter length(int len, int compressedLen) { | |
69 this.length = len; | |
70 this.compressedLength = compressedLen; | |
71 return this; | |
72 } | |
73 | |
74 public HeaderWriter parents(int parent1, int parent2) { | |
75 p1 = parent1; | |
76 p2 = parent2; | |
77 return this; | |
78 } | |
79 | |
80 public HeaderWriter linkRevision(int linkRevision) { | |
81 linkRev = linkRevision; | |
82 return this; | |
83 } | |
84 | |
85 public HeaderWriter nodeid(Nodeid n) { | |
86 nodeid = n.toByteArray(); | |
87 return this; | |
88 } | |
89 | |
90 public HeaderWriter nodeid(byte[] nodeidBytes) { | |
91 nodeid = nodeidBytes; | |
92 return this; | |
93 } | |
94 | |
95 public void serialize(DataSerializer out) throws IOException { | |
96 header.clear(); | |
97 if (offset == 0) { | |
98 int version = 1 /* RevlogNG */; | |
99 if (isInline) { | |
100 final int INLINEDATA = 1 << 16; // FIXME extract constant | |
101 version |= INLINEDATA; | |
102 } | |
103 header.putInt(version); | |
104 header.putInt(0); | |
105 } else { | |
106 header.putLong(offset << 16); | |
107 } | |
108 header.putInt(compressedLength); | |
109 header.putInt(length); | |
110 header.putInt(baseRev); | |
111 header.putInt(linkRev); | |
112 header.putInt(p1); | |
113 header.putInt(p2); | |
114 header.put(nodeid); | |
115 // assume 12 bytes left are zeros | |
116 out.write(header.array(), 0, header.capacity()); | |
117 | |
118 // regardless whether it's inline or separate data, | |
119 // offset field always represent cumulative compressedLength | |
120 // (while offset in the index file with inline==true differs by n*sizeof(header), where n is entry's position in the file) | |
121 offset += compressedLength; | |
122 } | |
123 | |
124 public int serializeLength() { | |
125 return header.capacity(); | |
126 } | |
127 } | |
128 | |
129 private final DigestHelper dh = new DigestHelper(); | 39 private final DigestHelper dh = new DigestHelper(); |
130 private final RevlogCompressor revlogDataZip; | 40 private final RevlogCompressor revlogDataZip; |
131 | |
132 | |
133 public RevlogStreamWriter(SessionContext ctx, RevlogStream stream) { | |
134 revlogDataZip = new RevlogCompressor(ctx); | |
135 } | |
136 | |
137 private int lastEntryBase, lastEntryIndex; | 41 private int lastEntryBase, lastEntryIndex; |
138 private byte[] lastEntryContent; | 42 private byte[] lastEntryContent; |
139 private Nodeid lastEntryRevision; | 43 private Nodeid lastEntryRevision; |
140 private IntMap<Nodeid> revisionCache = new IntMap<Nodeid>(32); | 44 private IntMap<Nodeid> revisionCache = new IntMap<Nodeid>(32); |
141 | 45 private RevlogStream revlogStream; |
142 public void addRevision(byte[] content, int linkRevision, int p1, int p2) { | 46 |
47 public RevlogStreamWriter(SessionContext ctx, RevlogStream stream) { | |
48 assert ctx != null; | |
49 assert stream != null; | |
50 | |
51 revlogDataZip = new RevlogCompressor(ctx); | |
52 revlogStream = stream; | |
53 } | |
54 | |
55 /** | |
56 * @return nodeid of added revision | |
57 */ | |
58 public Nodeid addRevision(byte[] content, int linkRevision, int p1, int p2) { | |
59 lastEntryRevision = Nodeid.NULL; | |
143 int revCount = revlogStream.revisionCount(); | 60 int revCount = revlogStream.revisionCount(); |
144 lastEntryIndex = revCount == 0 ? NO_REVISION : revCount - 1; | 61 lastEntryIndex = revCount == 0 ? NO_REVISION : revCount - 1; |
145 populateLastEntry(); | 62 populateLastEntry(); |
146 // | 63 // |
147 PatchGenerator pg = new PatchGenerator(); | 64 PatchGenerator pg = new PatchGenerator(); |
150 | 67 |
151 final boolean writeComplete = preferCompleteOverPatch(patchSerializedLength, content.length); | 68 final boolean writeComplete = preferCompleteOverPatch(patchSerializedLength, content.length); |
152 DataSerializer.DataSource dataSource = writeComplete ? new DataSerializer.ByteArrayDataSource(content) : patch.new PatchDataSource(); | 69 DataSerializer.DataSource dataSource = writeComplete ? new DataSerializer.ByteArrayDataSource(content) : patch.new PatchDataSource(); |
153 revlogDataZip.reset(dataSource); | 70 revlogDataZip.reset(dataSource); |
154 final int compressedLen; | 71 final int compressedLen; |
155 final boolean useUncompressedData = preferCompressedOverComplete(revlogDataZip.getCompressedLength(), dataSource.serializeLength()); | 72 final boolean useCompressedData = preferCompressedOverComplete(revlogDataZip.getCompressedLength(), dataSource.serializeLength()); |
156 if (useUncompressedData) { | 73 if (useCompressedData) { |
74 compressedLen= revlogDataZip.getCompressedLength(); | |
75 } else { | |
157 // compression wasn't too effective, | 76 // compression wasn't too effective, |
158 compressedLen = dataSource.serializeLength() + 1 /*1 byte for 'u' - uncompressed prefix byte*/; | 77 compressedLen = dataSource.serializeLength() + 1 /*1 byte for 'u' - uncompressed prefix byte*/; |
159 } else { | |
160 compressedLen= revlogDataZip.getCompressedLength(); | |
161 } | 78 } |
162 // | 79 // |
163 Nodeid p1Rev = revision(p1); | 80 Nodeid p1Rev = revision(p1); |
164 Nodeid p2Rev = revision(p2); | 81 Nodeid p2Rev = revision(p2); |
165 byte[] revisionNodeidBytes = dh.sha1(p1Rev, p2Rev, content).asBinary(); | 82 byte[] revisionNodeidBytes = dh.sha1(p1Rev, p2Rev, content).asBinary(); |
175 revlogHeader.length(content.length, compressedLen); | 92 revlogHeader.length(content.length, compressedLen); |
176 revlogHeader.nodeid(revisionNodeidBytes); | 93 revlogHeader.nodeid(revisionNodeidBytes); |
177 revlogHeader.linkRevision(linkRevision); | 94 revlogHeader.linkRevision(linkRevision); |
178 revlogHeader.parents(p1, p2); | 95 revlogHeader.parents(p1, p2); |
179 revlogHeader.baseRevision(writeComplete ? lastEntryIndex+1 : lastEntryBase); | 96 revlogHeader.baseRevision(writeComplete ? lastEntryIndex+1 : lastEntryBase); |
97 revlogHeader.offset(revlogStream.newEntryOffset()); | |
180 // | 98 // |
181 revlogHeader.serialize(indexFile); | 99 revlogHeader.serialize(indexFile); |
182 | 100 |
183 if (isInlineData) { | 101 if (isInlineData) { |
184 dataFile = indexFile; | 102 dataFile = indexFile; |
185 } else { | 103 } else { |
186 dataFile = revlogStream.getDataStreamWriter(); | 104 dataFile = revlogStream.getDataStreamWriter(); |
187 } | 105 } |
188 activeFile = dataFile; | 106 activeFile = dataFile; |
189 if (useUncompressedData) { | 107 if (useCompressedData) { |
190 dataFile.writeByte((byte) 'u'); | |
191 dataSource.serialize(dataFile); | |
192 } else { | |
193 int actualCompressedLenWritten = revlogDataZip.writeCompressedData(dataFile); | 108 int actualCompressedLenWritten = revlogDataZip.writeCompressedData(dataFile); |
194 if (actualCompressedLenWritten != compressedLen) { | 109 if (actualCompressedLenWritten != compressedLen) { |
195 throw new HgInvalidStateException(String.format("Expected %d bytes of compressed data, but actually wrote %d in %s", compressedLen, actualCompressedLenWritten, revlogStream.getDataFileName())); | 110 throw new HgInvalidStateException(String.format("Expected %d bytes of compressed data, but actually wrote %d in %s", compressedLen, actualCompressedLenWritten, revlogStream.getDataFileName())); |
196 } | 111 } |
112 } else { | |
113 dataFile.writeByte((byte) 'u'); | |
114 dataSource.serialize(dataFile); | |
197 } | 115 } |
198 | 116 |
199 lastEntryContent = content; | 117 lastEntryContent = content; |
200 lastEntryBase = revlogHeader.baseRevision(); | 118 lastEntryBase = revlogHeader.baseRevision(); |
201 lastEntryIndex++; | 119 lastEntryIndex++; |
213 indexFile.done(); | 131 indexFile.done(); |
214 if (dataFile != null && dataFile != indexFile) { | 132 if (dataFile != null && dataFile != indexFile) { |
215 dataFile.done(); | 133 dataFile.done(); |
216 } | 134 } |
217 } | 135 } |
218 } | 136 return lastEntryRevision; |
219 | 137 } |
220 private RevlogStream revlogStream; | 138 |
221 private Nodeid revision(int revisionIndex) { | 139 private Nodeid revision(int revisionIndex) { |
222 if (revisionIndex == NO_REVISION) { | 140 if (revisionIndex == NO_REVISION) { |
223 return Nodeid.NULL; | 141 return Nodeid.NULL; |
224 } | 142 } |
225 Nodeid n = revisionCache.get(revisionIndex); | 143 Nodeid n = revisionCache.get(revisionIndex); |
266 | 184 |
267 // true if length obtained with effort is worth it | 185 // true if length obtained with effort is worth it |
268 private static boolean decideWorthEffort(int lengthWithExtraEffort, int lengthWithoutEffort) { | 186 private static boolean decideWorthEffort(int lengthWithExtraEffort, int lengthWithoutEffort) { |
269 return lengthWithExtraEffort < (/* 3/4 of original */lengthWithoutEffort - (lengthWithoutEffort >>> 2)); | 187 return lengthWithExtraEffort < (/* 3/4 of original */lengthWithoutEffort - (lengthWithoutEffort >>> 2)); |
270 } | 188 } |
189 | |
190 /*XXX public because HgCloneCommand uses it*/ | |
191 public static class HeaderWriter implements DataSerializer.DataSource { | |
192 private final ByteBuffer header; | |
193 private final boolean isInline; | |
194 private long offset; | |
195 private int length, compressedLength; | |
196 private int baseRev, linkRev, p1, p2; | |
197 private byte[] nodeid; | |
198 | |
199 public HeaderWriter(boolean inline) { | |
200 isInline = inline; | |
201 header = ByteBuffer.allocate(REVLOGV1_RECORD_SIZE); | |
202 } | |
203 | |
204 public HeaderWriter offset(long offset) { | |
205 this.offset = offset; | |
206 return this; | |
207 } | |
208 | |
209 public int baseRevision() { | |
210 return baseRev; | |
211 } | |
212 | |
213 public HeaderWriter baseRevision(int baseRevision) { | |
214 this.baseRev = baseRevision; | |
215 return this; | |
216 } | |
217 | |
218 public HeaderWriter length(int len, int compressedLen) { | |
219 this.length = len; | |
220 this.compressedLength = compressedLen; | |
221 return this; | |
222 } | |
223 | |
224 public HeaderWriter parents(int parent1, int parent2) { | |
225 p1 = parent1; | |
226 p2 = parent2; | |
227 return this; | |
228 } | |
229 | |
230 public HeaderWriter linkRevision(int linkRevision) { | |
231 linkRev = linkRevision; | |
232 return this; | |
233 } | |
234 | |
235 public HeaderWriter nodeid(Nodeid n) { | |
236 nodeid = n.toByteArray(); | |
237 return this; | |
238 } | |
239 | |
240 public HeaderWriter nodeid(byte[] nodeidBytes) { | |
241 nodeid = nodeidBytes; | |
242 return this; | |
243 } | |
244 | |
245 public void serialize(DataSerializer out) throws IOException { | |
246 header.clear(); | |
247 if (offset == 0) { | |
248 int version = 1 /* RevlogNG */; | |
249 if (isInline) { | |
250 final int INLINEDATA = 1 << 16; // FIXME extract constant | |
251 version |= INLINEDATA; | |
252 } | |
253 header.putInt(version); | |
254 header.putInt(0); | |
255 } else { | |
256 header.putLong(offset << 16); | |
257 } | |
258 header.putInt(compressedLength); | |
259 header.putInt(length); | |
260 header.putInt(baseRev); | |
261 header.putInt(linkRev); | |
262 header.putInt(p1); | |
263 header.putInt(p2); | |
264 header.put(nodeid); | |
265 // assume 12 bytes left are zeros | |
266 out.write(header.array(), 0, header.capacity()); | |
267 | |
268 // regardless whether it's inline or separate data, | |
269 // offset field always represent cumulative compressedLength | |
270 // (while offset in the index file with inline==true differs by n*sizeof(header), where n is entry's position in the file) | |
271 offset += compressedLength; | |
272 } | |
273 | |
274 public int serializeLength() { | |
275 return header.capacity(); | |
276 } | |
277 } | |
271 } | 278 } |