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 }