View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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  /** Formats and writes blocks for {@link ReftableWriter}. */
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 			// Insanely long names need a larger block size.
156 			throw blockSizeTooSmall(entry);
157 		}
158 	}
159 
160 	boolean tryAdd(Entry entry) {
161 		if (entry instanceof ObjEntry
162 				&& computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) {
163 			// If the ObjEntry has so many ref block pointers that its
164 			// encoding overflows any block, reconfigure it to tell readers to
165 			// instead scan all refs for this ObjectId. That significantly
166 			// shrinks the entry to a very small size, which may now fit into
167 			// this block.
168 			((ObjEntry) entry).markScanRequired();
169 		}
170 
171 		if (tryAdd(entry, true)) {
172 			return true;
173 		} else if (nextShouldBeRestart()) {
174 			// It was time for another restart, but the entry doesn't fit
175 			// with its complete key, as the block is nearly full. Try to
176 			// force it to fit with prefix compression rather than waste
177 			// the tail of the block with padding.
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 /* "refs/" */ && keyType == REF_BLOCK_TYPE) {
192 				// Force restart points at transitions between namespaces
193 				// such as "refs/heads/" to "refs/tags/".
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 // 4-byte block header
230 				+ entryBytes
231 				+ restartCnt * 3 // restart_offset
232 				+ 2; // 2-byte restart_count
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 		// Compute size required to fit this entry by itself.
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 			// Nothing in a delete log record.
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 // tz
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 }