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