1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.internal.storage.reftable;
45
46 import static org.eclipse.jgit.lib.Constants.CHARSET;
47 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
48 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
49 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
50 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
51 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
52 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
53 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
54 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
55 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
56 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
57 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
58 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
59 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
60 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
61 import static org.eclipse.jgit.internal.storage.reftable.ReftableOutputStream.computeVarintSize;
62 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
63 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
64
65 import java.io.IOException;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.List;
69
70 import org.eclipse.jgit.internal.JGitText;
71 import org.eclipse.jgit.lib.ObjectId;
72 import org.eclipse.jgit.lib.PersonIdent;
73 import org.eclipse.jgit.lib.Ref;
74 import org.eclipse.jgit.util.IntList;
75 import org.eclipse.jgit.util.LongList;
76 import org.eclipse.jgit.util.NB;
77
78
79 class BlockWriter {
80 private final byte blockType;
81 private final byte keyType;
82 private final List<Entry> entries;
83 private final int blockLimitBytes;
84 private final int restartInterval;
85
86 private int entriesSumBytes;
87 private int restartCnt;
88
89 BlockWriter(byte type, byte kt, int bs, int ri) {
90 blockType = type;
91 keyType = kt;
92 blockLimitBytes = bs;
93 restartInterval = ri;
94 entries = new ArrayList<>(estimateEntryCount(type, kt, bs));
95 }
96
97 private static int estimateEntryCount(byte blockType, byte keyType,
98 int blockLimitBytes) {
99 double avgBytesPerEntry;
100 switch (blockType) {
101 case REF_BLOCK_TYPE:
102 default:
103 avgBytesPerEntry = 35.31;
104 break;
105
106 case OBJ_BLOCK_TYPE:
107 avgBytesPerEntry = 4.19;
108 break;
109
110 case LOG_BLOCK_TYPE:
111 avgBytesPerEntry = 101.14;
112 break;
113
114 case INDEX_BLOCK_TYPE:
115 switch (keyType) {
116 case REF_BLOCK_TYPE:
117 case LOG_BLOCK_TYPE:
118 default:
119 avgBytesPerEntry = 27.44;
120 break;
121
122 case OBJ_BLOCK_TYPE:
123 avgBytesPerEntry = 11.57;
124 break;
125 }
126 }
127
128 int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry));
129 return Math.min(cnt, 4096);
130 }
131
132 byte blockType() {
133 return blockType;
134 }
135
136 boolean padBetweenBlocks() {
137 return padBetweenBlocks(blockType)
138 || (blockType == INDEX_BLOCK_TYPE && padBetweenBlocks(keyType));
139 }
140
141 static boolean padBetweenBlocks(byte type) {
142 return type == REF_BLOCK_TYPE || type == OBJ_BLOCK_TYPE;
143 }
144
145 byte[] lastKey() {
146 return entries.get(entries.size() - 1).key;
147 }
148
149 int currentSize() {
150 return computeBlockBytes(0, false);
151 }
152
153 void mustAdd(Entry entry) throws BlockSizeTooSmallException {
154 if (!tryAdd(entry, true)) {
155
156 throw blockSizeTooSmall(entry);
157 }
158 }
159
160 boolean tryAdd(Entry entry) {
161 if (entry instanceof ObjEntry
162 && computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) {
163
164
165
166
167
168 ((ObjEntry) entry).markScanRequired();
169 }
170
171 if (tryAdd(entry, true)) {
172 return true;
173 } else if (nextShouldBeRestart()) {
174
175
176
177
178 return tryAdd(entry, false);
179 }
180 return false;
181 }
182
183 private boolean tryAdd(Entry entry, boolean tryRestart) {
184 byte[] key = entry.key;
185 int prefixLen = 0;
186 boolean restart = tryRestart && nextShouldBeRestart();
187 if (!restart) {
188 Entry priorEntry = entries.get(entries.size() - 1);
189 byte[] prior = priorEntry.key;
190 prefixLen = commonPrefix(prior, prior.length, key);
191 if (prefixLen <= 5 && keyType == REF_BLOCK_TYPE) {
192
193
194 restart = true;
195 prefixLen = 0;
196 } else if (prefixLen == 0) {
197 restart = true;
198 }
199 }
200
201 entry.restart = restart;
202 entry.prefixLen = prefixLen;
203 int entryBytes = entry.sizeBytes();
204 if (computeBlockBytes(entryBytes, restart) > blockLimitBytes) {
205 return false;
206 }
207
208 entriesSumBytes += entryBytes;
209 entries.add(entry);
210 if (restart) {
211 restartCnt++;
212 }
213 return true;
214 }
215
216 private boolean nextShouldBeRestart() {
217 int cnt = entries.size();
218 return (cnt == 0 || ((cnt + 1) % restartInterval) == 0)
219 && restartCnt < MAX_RESTARTS;
220 }
221
222 private int computeBlockBytes(int entryBytes, boolean restart) {
223 return computeBlockBytes(
224 entriesSumBytes + entryBytes,
225 restartCnt + (restart ? 1 : 0));
226 }
227
228 private static int computeBlockBytes(int entryBytes, int restartCnt) {
229 return 4
230 + entryBytes
231 + restartCnt * 3
232 + 2;
233 }
234
235 void writeTo(ReftableOutputStream os) throws IOException {
236 os.beginBlock(blockType);
237 IntList restarts = new IntList(restartCnt);
238 for (Entry entry : entries) {
239 if (entry.restart) {
240 restarts.add(os.bytesWrittenInBlock());
241 }
242 entry.writeKey(os);
243 entry.writeValue(os);
244 }
245 if (restarts.size() == 0 || restarts.size() > MAX_RESTARTS) {
246 throw new IllegalStateException();
247 }
248 for (int i = 0; i < restarts.size(); i++) {
249 os.writeInt24(restarts.get(i));
250 }
251 os.writeInt16(restarts.size());
252 os.flushBlock();
253 }
254
255 private BlockSizeTooSmallException blockSizeTooSmall(Entry entry) {
256
257 int min = FILE_HEADER_LEN + computeBlockBytes(entry.sizeBytes(), 1);
258 return new BlockSizeTooSmallException(min);
259 }
260
261 static int commonPrefix(byte[] a, int n, byte[] b) {
262 int len = Math.min(n, Math.min(a.length, b.length));
263 for (int i = 0; i < len; i++) {
264 if (a[i] != b[i]) {
265 return i;
266 }
267 }
268 return len;
269 }
270
271 static int encodeSuffixAndType(int sfx, int valueType) {
272 return (sfx << 3) | valueType;
273 }
274
275 static int compare(
276 byte[] a, int ai, int aLen,
277 byte[] b, int bi, int bLen) {
278 int aEnd = ai + aLen;
279 int bEnd = bi + bLen;
280 while (ai < aEnd && bi < bEnd) {
281 int c = (a[ai++] & 0xff) - (b[bi++] & 0xff);
282 if (c != 0) {
283 return c;
284 }
285 }
286 return aLen - bLen;
287 }
288
289 static abstract class Entry {
290 static int compare(Entry ea, Entry eb) {
291 byte[] a = ea.key;
292 byte[] b = eb.key;
293 return BlockWriter.compare(a, 0, a.length, b, 0, b.length);
294 }
295
296 final byte[] key;
297 int prefixLen;
298 boolean restart;
299
300 Entry(byte[] key) {
301 this.key = key;
302 }
303
304 void writeKey(ReftableOutputStream os) {
305 int sfxLen = key.length - prefixLen;
306 os.writeVarint(prefixLen);
307 os.writeVarint(encodeSuffixAndType(sfxLen, valueType()));
308 os.write(key, prefixLen, sfxLen);
309 }
310
311 int sizeBytes() {
312 int sfxLen = key.length - prefixLen;
313 int sfx = encodeSuffixAndType(sfxLen, valueType());
314 return computeVarintSize(prefixLen)
315 + computeVarintSize(sfx)
316 + sfxLen
317 + valueSize();
318 }
319
320 abstract byte blockType();
321 abstract int valueType();
322 abstract int valueSize();
323 abstract void writeValue(ReftableOutputStream os) throws IOException;
324 }
325
326 static class IndexEntry extends Entry {
327 private final long blockPosition;
328
329 IndexEntry(byte[] key, long blockPosition) {
330 super(key);
331 this.blockPosition = blockPosition;
332 }
333
334 @Override
335 byte blockType() {
336 return INDEX_BLOCK_TYPE;
337 }
338
339 @Override
340 int valueType() {
341 return 0;
342 }
343
344 @Override
345 int valueSize() {
346 return computeVarintSize(blockPosition);
347 }
348
349 @Override
350 void writeValue(ReftableOutputStream os) {
351 os.writeVarint(blockPosition);
352 }
353 }
354
355 static class RefEntry extends Entry {
356 final Ref ref;
357 final long updateIndexDelta;
358
359 RefEntry(Ref ref, long updateIndexDelta) {
360 super(nameUtf8(ref));
361 this.ref = ref;
362 this.updateIndexDelta = updateIndexDelta;
363 }
364
365 @Override
366 byte blockType() {
367 return REF_BLOCK_TYPE;
368 }
369
370 @Override
371 int valueType() {
372 if (ref.isSymbolic()) {
373 return VALUE_SYMREF;
374 } else if (ref.getStorage() == NEW && ref.getObjectId() == null) {
375 return VALUE_NONE;
376 } else if (ref.getPeeledObjectId() != null) {
377 return VALUE_2ID;
378 } else {
379 return VALUE_1ID;
380 }
381 }
382
383 @Override
384 int valueSize() {
385 int n = computeVarintSize(updateIndexDelta);
386 switch (valueType()) {
387 case VALUE_NONE:
388 return n;
389 case VALUE_1ID:
390 return n + OBJECT_ID_LENGTH;
391 case VALUE_2ID:
392 return n + 2 * OBJECT_ID_LENGTH;
393 case VALUE_SYMREF:
394 if (ref.isSymbolic()) {
395 int nameLen = nameUtf8(ref.getTarget()).length;
396 return n + computeVarintSize(nameLen) + nameLen;
397 }
398 }
399 throw new IllegalStateException();
400 }
401
402 @Override
403 void writeValue(ReftableOutputStream os) throws IOException {
404 os.writeVarint(updateIndexDelta);
405 switch (valueType()) {
406 case VALUE_NONE:
407 return;
408
409 case VALUE_1ID: {
410 ObjectId id1 = ref.getObjectId();
411 if (!ref.isPeeled()) {
412 throw new IOException(JGitText.get().peeledRefIsRequired);
413 } else if (id1 == null) {
414 throw new IOException(JGitText.get().invalidId0);
415 }
416 os.writeId(id1);
417 return;
418 }
419
420 case VALUE_2ID: {
421 ObjectId id1 = ref.getObjectId();
422 ObjectId id2 = ref.getPeeledObjectId();
423 if (!ref.isPeeled()) {
424 throw new IOException(JGitText.get().peeledRefIsRequired);
425 } else if (id1 == null || id2 == null) {
426 throw new IOException(JGitText.get().invalidId0);
427 }
428 os.writeId(id1);
429 os.writeId(id2);
430 return;
431 }
432
433 case VALUE_SYMREF:
434 if (ref.isSymbolic()) {
435 os.writeVarintString(ref.getTarget().getName());
436 return;
437 }
438 }
439 throw new IllegalStateException();
440 }
441
442 private static byte[] nameUtf8(Ref ref) {
443 return ref.getName().getBytes(CHARSET);
444 }
445 }
446
447 static class ObjEntry extends Entry {
448 final LongList blockPos;
449
450 ObjEntry(int idLen, ObjectId id, LongList blockPos) {
451 super(key(idLen, id));
452 this.blockPos = blockPos;
453 }
454
455 private static byte[] key(int idLen, ObjectId id) {
456 byte[] key = new byte[OBJECT_ID_LENGTH];
457 id.copyRawTo(key, 0);
458 if (idLen < OBJECT_ID_LENGTH) {
459 return Arrays.copyOf(key, idLen);
460 }
461 return key;
462 }
463
464 void markScanRequired() {
465 blockPos.clear();
466 }
467
468 @Override
469 byte blockType() {
470 return OBJ_BLOCK_TYPE;
471 }
472
473 @Override
474 int valueType() {
475 int cnt = blockPos.size();
476 return cnt != 0 && cnt <= VALUE_TYPE_MASK ? cnt : 0;
477 }
478
479 @Override
480 int valueSize() {
481 int cnt = blockPos.size();
482 if (cnt == 0) {
483 return computeVarintSize(0);
484 }
485
486 int n = 0;
487 if (cnt > VALUE_TYPE_MASK) {
488 n += computeVarintSize(cnt);
489 }
490 n += computeVarintSize(blockPos.get(0));
491 for (int j = 1; j < cnt; j++) {
492 long prior = blockPos.get(j - 1);
493 long b = blockPos.get(j);
494 n += computeVarintSize(b - prior);
495 }
496 return n;
497 }
498
499 @Override
500 void writeValue(ReftableOutputStream os) throws IOException {
501 int cnt = blockPos.size();
502 if (cnt == 0) {
503 os.writeVarint(0);
504 return;
505 }
506
507 if (cnt > VALUE_TYPE_MASK) {
508 os.writeVarint(cnt);
509 }
510 os.writeVarint(blockPos.get(0));
511 for (int j = 1; j < cnt; j++) {
512 long prior = blockPos.get(j - 1);
513 long b = blockPos.get(j);
514 os.writeVarint(b - prior);
515 }
516 }
517 }
518
519 static class DeleteLogEntry extends Entry {
520 DeleteLogEntry(String refName, long updateIndex) {
521 super(LogEntry.key(refName, updateIndex));
522 }
523
524 @Override
525 byte blockType() {
526 return LOG_BLOCK_TYPE;
527 }
528
529 @Override
530 int valueType() {
531 return LOG_NONE;
532 }
533
534 @Override
535 int valueSize() {
536 return 0;
537 }
538
539 @Override
540 void writeValue(ReftableOutputStream os) {
541
542 }
543 }
544
545 static class LogEntry extends Entry {
546 final ObjectId oldId;
547 final ObjectId newId;
548 final long timeSecs;
549 final short tz;
550 final byte[] name;
551 final byte[] email;
552 final byte[] msg;
553
554 LogEntry(String refName, long updateIndex, PersonIdent who,
555 ObjectId oldId, ObjectId newId, String message) {
556 super(key(refName, updateIndex));
557
558 this.oldId = oldId;
559 this.newId = newId;
560 this.timeSecs = who.getWhen().getTime() / 1000L;
561 this.tz = (short) who.getTimeZoneOffset();
562 this.name = who.getName().getBytes(CHARSET);
563 this.email = who.getEmailAddress().getBytes(CHARSET);
564 this.msg = message.getBytes(CHARSET);
565 }
566
567 static byte[] key(String ref, long index) {
568 byte[] name = ref.getBytes(CHARSET);
569 byte[] key = Arrays.copyOf(name, name.length + 1 + 8);
570 NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index));
571 return key;
572 }
573
574 @Override
575 byte blockType() {
576 return LOG_BLOCK_TYPE;
577 }
578
579 @Override
580 int valueType() {
581 return LOG_DATA;
582 }
583
584 @Override
585 int valueSize() {
586 return 2 * OBJECT_ID_LENGTH
587 + computeVarintSize(name.length) + name.length
588 + computeVarintSize(email.length) + email.length
589 + computeVarintSize(timeSecs)
590 + 2
591 + computeVarintSize(msg.length) + msg.length;
592 }
593
594 @Override
595 void writeValue(ReftableOutputStream os) {
596 os.writeId(oldId);
597 os.writeId(newId);
598 os.writeVarintString(name);
599 os.writeVarintString(email);
600 os.writeVarint(timeSecs);
601 os.writeInt16(tz);
602 os.writeVarintString(msg);
603 }
604 }
605 }