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 }