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