View Javadoc
1   /*
2    * Copyright (C) 2012, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import java.io.DataInput;
14  import java.io.IOException;
15  import java.io.InputStream;
16  import java.text.MessageFormat;
17  import java.util.Arrays;
18  
19  import org.eclipse.jgit.internal.JGitText;
20  import org.eclipse.jgit.lib.AnyObjectId;
21  import org.eclipse.jgit.lib.Constants;
22  import org.eclipse.jgit.lib.ObjectId;
23  import org.eclipse.jgit.lib.ObjectIdOwnerMap;
24  import org.eclipse.jgit.util.IO;
25  import org.eclipse.jgit.util.NB;
26  
27  import com.googlecode.javaewah.EWAHCompressedBitmap;
28  
29  /**
30   * Support for the pack bitmap index v1 format.
31   *
32   * @see PackBitmapIndex
33   */
34  class PackBitmapIndexV1 extends BasePackBitmapIndex {
35  	static final byte[] MAGIC = { 'B', 'I', 'T', 'M' };
36  	static final int OPT_FULL = 1;
37  
38  	private static final int MAX_XOR_OFFSET = 126;
39  
40  	private final PackIndex packIndex;
41  	private final PackReverseIndex reverseIndex;
42  	private final EWAHCompressedBitmap commits;
43  	private final EWAHCompressedBitmap trees;
44  	private final EWAHCompressedBitmap blobs;
45  	private final EWAHCompressedBitmap tags;
46  
47  	private final ObjectIdOwnerMap<StoredBitmap> bitmaps;
48  
49  	PackBitmapIndexV1(final InputStream fd, PackIndex packIndex,
50  			PackReverseIndex reverseIndex) throws IOException {
51  		super(new ObjectIdOwnerMap<StoredBitmap>());
52  		this.packIndex = packIndex;
53  		this.reverseIndex = reverseIndex;
54  		this.bitmaps = getBitmaps();
55  
56  		final byte[] scratch = new byte[32];
57  		IO.readFully(fd, scratch, 0, scratch.length);
58  
59  		// Check the magic bytes
60  		for (int i = 0; i < MAGIC.length; i++) {
61  			if (scratch[i] != MAGIC[i]) {
62  				byte[] actual = new byte[MAGIC.length];
63  				System.arraycopy(scratch, 0, actual, 0, MAGIC.length);
64  				throw new IOException(MessageFormat.format(
65  						JGitText.get().expectedGot, Arrays.toString(MAGIC),
66  						Arrays.toString(actual)));
67  			}
68  		}
69  
70  		// Read the version (2 bytes)
71  		final int version = NB.decodeUInt16(scratch, 4);
72  		if (version != 1)
73  			throw new IOException(MessageFormat.format(
74  					JGitText.get().unsupportedPackIndexVersion,
75  					Integer.valueOf(version)));
76  
77  		// Read the options (2 bytes)
78  		final int opts = NB.decodeUInt16(scratch, 6);
79  		if ((opts & OPT_FULL) == 0)
80  			throw new IOException(MessageFormat.format(
81  					JGitText.get().expectedGot, Integer.valueOf(OPT_FULL),
82  					Integer.valueOf(opts)));
83  
84  		// Read the number of entries (1 int32)
85  		long numEntries = NB.decodeUInt32(scratch, 8);
86  		if (numEntries > Integer.MAX_VALUE)
87  			throw new IOException(JGitText.get().indexFileIsTooLargeForJgit);
88  
89  		// Checksum applied on the bottom of the corresponding pack file.
90  		this.packChecksum = new byte[20];
91  		System.arraycopy(scratch, 12, packChecksum, 0, packChecksum.length);
92  
93  		// Read the bitmaps for the Git types
94  		SimpleDataInput dataInput = new SimpleDataInput(fd);
95  		this.commits = readBitmap(dataInput);
96  		this.trees = readBitmap(dataInput);
97  		this.blobs = readBitmap(dataInput);
98  		this.tags = readBitmap(dataInput);
99  
100 		// An entry is object id, xor offset, flag byte, and a length encoded
101 		// bitmap. The object id is an int32 of the nth position sorted by name.
102 		// The xor offset is a single byte offset back in the list of entries.
103 		StoredBitmap[] recentBitmaps = new StoredBitmap[MAX_XOR_OFFSET];
104 		for (int i = 0; i < (int) numEntries; i++) {
105 			IO.readFully(fd, scratch, 0, 6);
106 			int nthObjectId = NB.decodeInt32(scratch, 0);
107 			int xorOffset = scratch[4];
108 			int flags = scratch[5];
109 			EWAHCompressedBitmap bitmap = readBitmap(dataInput);
110 
111 			if (nthObjectId < 0)
112 				throw new IOException(MessageFormat.format(
113 						JGitText.get().invalidId, String.valueOf(nthObjectId)));
114 			if (xorOffset < 0)
115 				throw new IOException(MessageFormat.format(
116 						JGitText.get().invalidId, String.valueOf(xorOffset)));
117 			if (xorOffset > MAX_XOR_OFFSET)
118 				throw new IOException(MessageFormat.format(
119 						JGitText.get().expectedLessThanGot,
120 						String.valueOf(MAX_XOR_OFFSET),
121 						String.valueOf(xorOffset)));
122 			if (xorOffset > i)
123 				throw new IOException(MessageFormat.format(
124 						JGitText.get().expectedLessThanGot, String.valueOf(i),
125 						String.valueOf(xorOffset)));
126 
127 			ObjectId objectId = packIndex.getObjectId(nthObjectId);
128 			StoredBitmap xorBitmap = null;
129 			if (xorOffset > 0) {
130 				int index = (i - xorOffset);
131 				xorBitmap = recentBitmaps[index % recentBitmaps.length];
132 				if (xorBitmap == null)
133 					throw new IOException(MessageFormat.format(
134 							JGitText.get().invalidId,
135 							String.valueOf(xorOffset)));
136 			}
137 
138 			StoredBitmap sb = new StoredBitmap(
139 					objectId, bitmap, xorBitmap, flags);
140 			bitmaps.add(sb);
141 			recentBitmaps[i % recentBitmaps.length] = sb;
142 		}
143 	}
144 
145 	/** {@inheritDoc} */
146 	@Override
147 	public int findPosition(AnyObjectId objectId) {
148 		long offset = packIndex.findOffset(objectId);
149 		if (offset == -1)
150 			return -1;
151 		return reverseIndex.findPostion(offset);
152 	}
153 
154 	/** {@inheritDoc} */
155 	@Override
156 	public ObjectId getObject(int position) throws IllegalArgumentException {
157 		ObjectId objectId = reverseIndex.findObjectByPosition(position);
158 		if (objectId == null)
159 			throw new IllegalArgumentException();
160 		return objectId;
161 	}
162 
163 	/** {@inheritDoc} */
164 	@Override
165 	public int getObjectCount() {
166 		return (int) packIndex.getObjectCount();
167 	}
168 
169 	/** {@inheritDoc} */
170 	@Override
171 	public EWAHCompressedBitmap ofObjectType(
172 			EWAHCompressedBitmap bitmap, int type) {
173 		switch (type) {
174 		case Constants.OBJ_BLOB:
175 			return blobs.and(bitmap);
176 		case Constants.OBJ_TREE:
177 			return trees.and(bitmap);
178 		case Constants.OBJ_COMMIT:
179 			return commits.and(bitmap);
180 		case Constants.OBJ_TAG:
181 			return tags.and(bitmap);
182 		}
183 		throw new IllegalArgumentException();
184 	}
185 
186 	/** {@inheritDoc} */
187 	@Override
188 	public int getBitmapCount() {
189 		return bitmaps.size();
190 	}
191 
192 	/** {@inheritDoc} */
193 	@Override
194 	public boolean equals(Object o) {
195 		// TODO(cranger): compare the pack checksum?
196 		if (o instanceof PackBitmapIndexV1)
197 			return getPackIndex() == ((PackBitmapIndexV1) o).getPackIndex();
198 		return false;
199 	}
200 
201 	/** {@inheritDoc} */
202 	@Override
203 	public int hashCode() {
204 		return getPackIndex().hashCode();
205 	}
206 
207 	PackIndex getPackIndex() {
208 		return packIndex;
209 	}
210 
211 	private static EWAHCompressedBitmap readBitmap(DataInput dataInput)
212 			throws IOException {
213 		EWAHCompressedBitmap bitmap = new EWAHCompressedBitmap();
214 		bitmap.deserialize(dataInput);
215 		return bitmap;
216 	}
217 }