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