1 /*
2 * Copyright (C) 2010, 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.lib;
45
46 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
47 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
48 import static org.eclipse.jgit.lib.Constants.encode;
49 import static org.eclipse.jgit.lib.FileMode.GITLINK;
50 import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
51 import static org.eclipse.jgit.lib.FileMode.TREE;
52
53 import java.io.IOException;
54
55 import org.eclipse.jgit.errors.CorruptObjectException;
56 import org.eclipse.jgit.internal.JGitText;
57 import org.eclipse.jgit.revwalk.RevBlob;
58 import org.eclipse.jgit.revwalk.RevCommit;
59 import org.eclipse.jgit.revwalk.RevTree;
60 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
61 import org.eclipse.jgit.util.TemporaryBuffer;
62
63 /**
64 * Mutable formatter to construct a single tree object.
65 *
66 * This formatter does not process subtrees. Callers must handle creating each
67 * subtree on their own.
68 *
69 * To maintain good performance for bulk operations, this formatter does not
70 * validate its input. Callers are responsible for ensuring the resulting tree
71 * object is correctly well formed by writing entries in the correct order.
72 */
73 public class TreeFormatter {
74 /**
75 * Compute the size of a tree entry record.
76 *
77 * This method can be used to estimate the correct size of a tree prior to
78 * allocating a formatter. Getting the size correct at allocation time
79 * ensures the internal buffer is sized correctly, reducing copying.
80 *
81 * @param mode
82 * the mode the entry will have.
83 * @param nameLen
84 * the length of the name, in bytes.
85 * @return the length of the record.
86 */
87 public static int entrySize(FileMode mode, int nameLen) {
88 return mode.copyToLength() + nameLen + OBJECT_ID_LENGTH + 2;
89 }
90
91 private byte[] buf;
92
93 private int ptr;
94
95 private TemporaryBuffer.Heap overflowBuffer;
96
97 /** Create an empty formatter with a default buffer size. */
98 public TreeFormatter() {
99 this(8192);
100 }
101
102 /**
103 * Create an empty formatter with the specified buffer size.
104 *
105 * @param size
106 * estimated size of the tree, in bytes. Callers can use
107 * {@link #entrySize(FileMode, int)} to estimate the size of each
108 * entry in advance of allocating the formatter.
109 */
110 public TreeFormatter(int size) {
111 buf = new byte[size];
112 }
113
114 /**
115 * Add a link to a submodule commit, mode is {@link FileMode#GITLINK}.
116 *
117 * @param name
118 * name of the entry.
119 * @param commit
120 * the ObjectId to store in this entry.
121 */
122 public void append(String name, RevCommit commit) {
123 append(name, GITLINK, commit);
124 }
125
126 /**
127 * Add a subtree, mode is {@link FileMode#TREE}.
128 *
129 * @param name
130 * name of the entry.
131 * @param tree
132 * the ObjectId to store in this entry.
133 */
134 public void append(String name, RevTree tree) {
135 append(name, TREE, tree);
136 }
137
138 /**
139 * Add a regular file, mode is {@link FileMode#REGULAR_FILE}.
140 *
141 * @param name
142 * name of the entry.
143 * @param blob
144 * the ObjectId to store in this entry.
145 */
146 public void append(String name, RevBlob blob) {
147 append(name, REGULAR_FILE, blob);
148 }
149
150 /**
151 * Append any entry to the tree.
152 *
153 * @param name
154 * name of the entry.
155 * @param mode
156 * mode describing the treatment of {@code id}.
157 * @param id
158 * the ObjectId to store in this entry.
159 */
160 public void append(String name, FileMode mode, AnyObjectId id) {
161 append(encode(name), mode, id);
162 }
163
164 /**
165 * Append any entry to the tree.
166 *
167 * @param name
168 * name of the entry. The name should be UTF-8 encoded, but file
169 * name encoding is not a well defined concept in Git.
170 * @param mode
171 * mode describing the treatment of {@code id}.
172 * @param id
173 * the ObjectId to store in this entry.
174 */
175 public void append(byte[] name, FileMode mode, AnyObjectId id) {
176 append(name, 0, name.length, mode, id);
177 }
178
179 /**
180 * Append any entry to the tree.
181 *
182 * @param nameBuf
183 * buffer holding the name of the entry. The name should be UTF-8
184 * encoded, but file name encoding is not a well defined concept
185 * in Git.
186 * @param namePos
187 * first position within {@code nameBuf} of the name data.
188 * @param nameLen
189 * number of bytes from {@code nameBuf} to use as the name.
190 * @param mode
191 * mode describing the treatment of {@code id}.
192 * @param id
193 * the ObjectId to store in this entry.
194 */
195 public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
196 AnyObjectId id) {
197 append(nameBuf, namePos, nameLen, mode, id, false);
198 }
199
200 /**
201 * Append any entry to the tree.
202 *
203 * @param nameBuf
204 * buffer holding the name of the entry. The name should be UTF-8
205 * encoded, but file name encoding is not a well defined concept
206 * in Git.
207 * @param namePos
208 * first position within {@code nameBuf} of the name data.
209 * @param nameLen
210 * number of bytes from {@code nameBuf} to use as the name.
211 * @param mode
212 * mode describing the treatment of {@code id}.
213 * @param id
214 * the ObjectId to store in this entry.
215 * @param allowEmptyName
216 * allow an empty filename (creating a corrupt tree)
217 * @since 4.6
218 */
219 public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
220 AnyObjectId id, boolean allowEmptyName) {
221 if (nameLen == 0 && !allowEmptyName) {
222 throw new IllegalArgumentException(
223 JGitText.get().invalidTreeZeroLengthName);
224 }
225 if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
226 id.copyRawTo(buf, ptr);
227 ptr += OBJECT_ID_LENGTH;
228
229 } else {
230 try {
231 fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
232 id.copyRawTo(overflowBuffer);
233 } catch (IOException badBuffer) {
234 // This should never occur.
235 throw new RuntimeException(badBuffer);
236 }
237 }
238 }
239
240 /**
241 * Append any entry to the tree.
242 *
243 * @param nameBuf
244 * buffer holding the name of the entry. The name should be UTF-8
245 * encoded, but file name encoding is not a well defined concept
246 * in Git.
247 * @param namePos
248 * first position within {@code nameBuf} of the name data.
249 * @param nameLen
250 * number of bytes from {@code nameBuf} to use as the name.
251 * @param mode
252 * mode describing the treatment of {@code id}.
253 * @param idBuf
254 * buffer holding the raw ObjectId of the entry.
255 * @param idPos
256 * first position within {@code idBuf} to copy the id from.
257 */
258 public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
259 byte[] idBuf, int idPos) {
260 if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
261 System.arraycopy(idBuf, idPos, buf, ptr, OBJECT_ID_LENGTH);
262 ptr += OBJECT_ID_LENGTH;
263
264 } else {
265 try {
266 fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
267 overflowBuffer.write(idBuf, idPos, OBJECT_ID_LENGTH);
268 } catch (IOException badBuffer) {
269 // This should never occur.
270 throw new RuntimeException(badBuffer);
271 }
272 }
273 }
274
275 private boolean fmtBuf(byte[] nameBuf, int namePos, int nameLen,
276 FileMode mode) {
277 if (buf == null || buf.length < ptr + entrySize(mode, nameLen))
278 return false;
279
280 mode.copyTo(buf, ptr);
281 ptr += mode.copyToLength();
282 buf[ptr++] = ' ';
283
284 System.arraycopy(nameBuf, namePos, buf, ptr, nameLen);
285 ptr += nameLen;
286 buf[ptr++] = 0;
287 return true;
288 }
289
290 private void fmtOverflowBuffer(byte[] nameBuf, int namePos, int nameLen,
291 FileMode mode) throws IOException {
292 if (buf != null) {
293 overflowBuffer = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
294 overflowBuffer.write(buf, 0, ptr);
295 buf = null;
296 }
297
298 mode.copyTo(overflowBuffer);
299 overflowBuffer.write((byte) ' ');
300 overflowBuffer.write(nameBuf, namePos, nameLen);
301 overflowBuffer.write((byte) 0);
302 }
303
304 /**
305 * Insert this tree and obtain its ObjectId.
306 *
307 * @param ins
308 * the inserter to store the tree.
309 * @return computed ObjectId of the tree
310 * @throws IOException
311 * the tree could not be stored.
312 */
313 public ObjectId insertTo(ObjectInserter ins) throws IOException {
314 if (buf != null)
315 return ins.insert(OBJ_TREE, buf, 0, ptr);
316
317 final long len = overflowBuffer.length();
318 return ins.insert(OBJ_TREE, len, overflowBuffer.openInputStream());
319 }
320
321 /**
322 * Compute the ObjectId for this tree
323 *
324 * @param ins
325 * @return ObjectId for this tree
326 */
327 public ObjectId computeId(ObjectInserter ins) {
328 if (buf != null)
329 return ins.idFor(OBJ_TREE, buf, 0, ptr);
330
331 final long len = overflowBuffer.length();
332 try {
333 return ins.idFor(OBJ_TREE, len, overflowBuffer.openInputStream());
334 } catch (IOException e) {
335 // this should never happen
336 throw new RuntimeException(e);
337 }
338 }
339
340 /**
341 * Copy this formatter's buffer into a byte array.
342 *
343 * This method is not efficient, as it needs to create a copy of the
344 * internal buffer in order to supply an array of the correct size to the
345 * caller. If the buffer is just to pass to an ObjectInserter, consider
346 * using {@link ObjectInserter#insert(TreeFormatter)} instead.
347 *
348 * @return a copy of this formatter's buffer.
349 */
350 public byte[] toByteArray() {
351 if (buf != null) {
352 byte[] r = new byte[ptr];
353 System.arraycopy(buf, 0, r, 0, ptr);
354 return r;
355 }
356
357 try {
358 return overflowBuffer.toByteArray();
359 } catch (IOException err) {
360 // This should never happen, its read failure on a byte array.
361 throw new RuntimeException(err);
362 }
363 }
364
365 @SuppressWarnings("nls")
366 @Override
367 public String toString() {
368 byte[] raw = toByteArray();
369
370 CanonicalTreeParser p = new CanonicalTreeParser();
371 p.reset(raw);
372
373 StringBuilder r = new StringBuilder();
374 r.append("Tree={");
375 if (!p.eof()) {
376 r.append('\n');
377 try {
378 new ObjectChecker().checkTree(raw);
379 } catch (CorruptObjectException error) {
380 r.append("*** ERROR: ").append(error.getMessage()).append("\n");
381 r.append('\n');
382 }
383 }
384 while (!p.eof()) {
385 final FileMode mode = p.getEntryFileMode();
386 r.append(mode);
387 r.append(' ');
388 r.append(Constants.typeString(mode.getObjectType()));
389 r.append(' ');
390 r.append(p.getEntryObjectId().name());
391 r.append(' ');
392 r.append(p.getEntryPathString());
393 r.append('\n');
394 p.next();
395 }
396 r.append("}");
397 return r.toString();
398 }
399 }