1 /* 2 * Copyright (C) 2008-2009, Google Inc. 3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 4 * and other copyright owners as documented in the project's IP log. 5 * 6 * This program and the accompanying materials are made available 7 * under the terms of the Eclipse Distribution License v1.0 which 8 * accompanies this distribution, is reproduced below, and is 9 * available at http://www.eclipse.org/org/documents/edl-v10.php 10 * 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or 14 * without modification, are permitted provided that the following 15 * conditions are met: 16 * 17 * - Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * - Redistributions in binary form must reproduce the above 21 * copyright notice, this list of conditions and the following 22 * disclaimer in the documentation and/or other materials provided 23 * with the distribution. 24 * 25 * - Neither the name of the Eclipse Foundation, Inc. nor the 26 * names of its contributors may be used to endorse or promote 27 * products derived from this software without specific prior 28 * written permission. 29 * 30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 */ 44 45 package org.eclipse.jgit.revwalk; 46 47 import static java.nio.charset.StandardCharsets.UTF_8; 48 49 import java.io.IOException; 50 import java.nio.charset.Charset; 51 import java.nio.charset.IllegalCharsetNameException; 52 import java.nio.charset.UnsupportedCharsetException; 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.Collections; 56 import java.util.List; 57 58 import org.eclipse.jgit.annotations.Nullable; 59 import org.eclipse.jgit.errors.IncorrectObjectTypeException; 60 import org.eclipse.jgit.errors.MissingObjectException; 61 import org.eclipse.jgit.lib.AnyObjectId; 62 import org.eclipse.jgit.lib.Constants; 63 import org.eclipse.jgit.lib.MutableObjectId; 64 import org.eclipse.jgit.lib.ObjectInserter; 65 import org.eclipse.jgit.lib.ObjectReader; 66 import org.eclipse.jgit.lib.PersonIdent; 67 import org.eclipse.jgit.util.RawParseUtils; 68 import org.eclipse.jgit.util.StringUtils; 69 70 /** 71 * A commit reference to a commit in the DAG. 72 */ 73 public class RevCommit extends RevObject { 74 private static final int STACK_DEPTH = 500; 75 76 /** 77 * Parse a commit from its canonical format. 78 * 79 * This method constructs a temporary revision pool, parses the commit as 80 * supplied, and returns it to the caller. Since the commit was built inside 81 * of a private revision pool its parent pointers will be initialized, but 82 * will not have their headers loaded. 83 * 84 * Applications are discouraged from using this API. Callers usually need 85 * more than one commit. Use 86 * {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)} to 87 * obtain a RevCommit from an existing repository. 88 * 89 * @param raw 90 * the canonical formatted commit to be parsed. 91 * @return the parsed commit, in an isolated revision pool that is not 92 * available to the caller. 93 */ 94 public static RevCommit parse(byte[] raw) { 95 try { 96 return parse(new RevWalk((ObjectReader) null), raw); 97 } catch (IOException ex) { 98 throw new RuntimeException(ex); 99 } 100 } 101 102 /** 103 * Parse a commit from its canonical format. 104 * <p> 105 * This method inserts the commit directly into the caller supplied revision 106 * pool, making it appear as though the commit exists in the repository, 107 * even if it doesn't. The repository under the pool is not affected. 108 * <p> 109 * The body of the commit (message, author, committer) is always retained in 110 * the returned {@code RevCommit}, even if the supplied {@code RevWalk} has 111 * been configured with {@code setRetainBody(false)}. 112 * 113 * @param rw 114 * the revision pool to allocate the commit within. The commit's 115 * tree and parent pointers will be obtained from this pool. 116 * @param raw 117 * the canonical formatted commit to be parsed. This buffer will 118 * be retained by the returned {@code RevCommit} and must not be 119 * modified by the caller. 120 * @return the parsed commit, in an isolated revision pool that is not 121 * available to the caller. 122 * @throws java.io.IOException 123 * in case of RevWalk initialization fails 124 */ 125 public static RevCommit parse(RevWalk rw, byte[] raw) throws IOException { 126 try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { 127 RevCommit r = rw.lookupCommit(fmt.idFor(Constants.OBJ_COMMIT, raw)); 128 r.parseCanonical(rw, raw); 129 r.buffer = raw; 130 return r; 131 } 132 } 133 134 static final RevCommit[] NO_PARENTS = {}; 135 136 private RevTree tree; 137 138 RevCommit[] parents; 139 140 int commitTime; // An int here for performance, overflows in 2038 141 142 int inDegree; 143 144 private byte[] buffer; 145 146 /** 147 * Create a new commit reference. 148 * 149 * @param id 150 * object name for the commit. 151 */ 152 protected RevCommit(AnyObjectId id) { 153 super(id); 154 } 155 156 @Override 157 void parseHeaders(RevWalk walk) throws MissingObjectException, 158 IncorrectObjectTypeException, IOException { 159 parseCanonical(walk, walk.getCachedBytes(this)); 160 } 161 162 @Override 163 void parseBody(RevWalk walk) throws MissingObjectException, 164 IncorrectObjectTypeException, IOException { 165 if (buffer == null) { 166 buffer = walk.getCachedBytes(this); 167 if ((flags & PARSED) == 0) 168 parseCanonical(walk, buffer); 169 } 170 } 171 172 void parseCanonical(RevWalk walk, byte[] raw) throws IOException { 173 if (!walk.shallowCommitsInitialized) { 174 walk.initializeShallowCommits(this); 175 } 176 177 final MutableObjectId idBuffer = walk.idBuffer; 178 idBuffer.fromString(raw, 5); 179 tree = walk.lookupTree(idBuffer); 180 181 int ptr = 46; 182 if (parents == null) { 183 RevCommit[] pList = new RevCommit[1]; 184 int nParents = 0; 185 for (;;) { 186 if (raw[ptr] != 'p') { 187 break; 188 } 189 idBuffer.fromString(raw, ptr + 7); 190 final RevCommit p = walk.lookupCommit(idBuffer); 191 if (nParents == 0) { 192 pList[nParents++] = p; 193 } else if (nParents == 1) { 194 pList = new RevCommit[] { pList[0], p }; 195 nParents = 2; 196 } else { 197 if (pList.length <= nParents) { 198 RevCommit[] old = pList; 199 pList = new RevCommit[pList.length + 32]; 200 System.arraycopy(old, 0, pList, 0, nParents); 201 } 202 pList[nParents++] = p; 203 } 204 ptr += 48; 205 } 206 if (nParents != pList.length) { 207 RevCommit[] old = pList; 208 pList = new RevCommit[nParents]; 209 System.arraycopy(old, 0, pList, 0, nParents); 210 } 211 parents = pList; 212 } 213 214 // extract time from "committer " 215 ptr = RawParseUtils.committer(raw, ptr); 216 if (ptr > 0) { 217 ptr = RawParseUtils.nextLF(raw, ptr, '>'); 218 219 // In 2038 commitTime will overflow unless it is changed to long. 220 commitTime = RawParseUtils.parseBase10(raw, ptr, null); 221 } 222 223 if (walk.isRetainBody()) { 224 buffer = raw; 225 } 226 flags |= PARSED; 227 } 228 229 /** {@inheritDoc} */ 230 @Override 231 public final int getType() { 232 return Constants.OBJ_COMMIT; 233 } 234 235 static void carryFlags(RevCommit c, int carry) { 236 FIFORevQueue q = carryFlags1(c, carry, 0); 237 if (q != null) 238 slowCarryFlags(q, carry); 239 } 240 241 private static FIFORevQueue carryFlags1(RevCommit c, int carry, int depth) { 242 for(;;) { 243 RevCommit[] pList = c.parents; 244 if (pList == null || pList.length == 0) 245 return null; 246 if (pList.length != 1) { 247 if (depth == STACK_DEPTH) 248 return defer(c); 249 for (int i = 1; i < pList.length; i++) { 250 RevCommit p = pList[i]; 251 if ((p.flags & carry) == carry) 252 continue; 253 p.flags |= carry; 254 FIFORevQueue q = carryFlags1(p, carry, depth + 1); 255 if (q != null) 256 return defer(q, carry, pList, i + 1); 257 } 258 } 259 260 c = pList[0]; 261 if ((c.flags & carry) == carry) 262 return null; 263 c.flags |= carry; 264 } 265 } 266 267 private static FIFORevQueue defer(RevCommit c) { 268 FIFORevQueue q = new FIFORevQueue(); 269 q.add(c); 270 return q; 271 } 272 273 private static FIFORevQueue/../../../org/eclipse/jgit/revwalk/FIFORevQueue.html#FIFORevQueue">FIFORevQueue defer(FIFORevQueue q, int carry, 274 RevCommit[] pList, int i) { 275 // In normal case the caller will run pList[0] in a tail recursive 276 // fashion by updating the variable. However the caller is unwinding 277 // the stack and will skip that pList[0] execution step. 278 carryOneStep(q, carry, pList[0]); 279 280 // Remaining parents (if any) need to have flags checked and be 281 // enqueued if they have ancestors. 282 for (; i < pList.length; i++) 283 carryOneStep(q, carry, pList[i]); 284 return q; 285 } 286 287 private static void slowCarryFlags(FIFORevQueue q, int carry) { 288 // Commits in q have non-null parent arrays and have set all 289 // flags in carry. This loop finishes copying over the graph. 290 for (RevCommit c; (c = q.next()) != null;) { 291 for (RevCommit p : c.parents) 292 carryOneStep(q, carry, p); 293 } 294 } 295 296 private static void carryOneStep(FIFORevQueue q, int carry, RevCommit c) { 297 if ((c.flags & carry) != carry) { 298 c.flags |= carry; 299 if (c.parents != null) 300 q.add(c); 301 } 302 } 303 304 /** 305 * Carry a RevFlag set on this commit to its parents. 306 * <p> 307 * If this commit is parsed, has parents, and has the supplied flag set on 308 * it we automatically add it to the parents, grand-parents, and so on until 309 * an unparsed commit or a commit with no parents is discovered. This 310 * permits applications to force a flag through the history chain when 311 * necessary. 312 * 313 * @param flag 314 * the single flag value to carry back onto parents. 315 */ 316 public void carry(RevFlag flag) { 317 final int carry = flags & flag.mask; 318 if (carry != 0) 319 carryFlags(this, carry); 320 } 321 322 /** 323 * Time from the "committer " line of the buffer. 324 * 325 * @return commit time 326 */ 327 public final int getCommitTime() { 328 return commitTime; 329 } 330 331 /** 332 * Get a reference to this commit's tree. 333 * 334 * @return tree of this commit. 335 */ 336 public final RevTree getTree() { 337 return tree; 338 } 339 340 /** 341 * Get the number of parent commits listed in this commit. 342 * 343 * @return number of parents; always a positive value but can be 0. 344 */ 345 public final int getParentCount() { 346 return parents.length; 347 } 348 349 /** 350 * Get the nth parent from this commit's parent list. 351 * 352 * @param nth 353 * parent index to obtain. Must be in the range 0 through 354 * {@link #getParentCount()}-1. 355 * @return the specified parent. 356 * @throws java.lang.ArrayIndexOutOfBoundsException 357 * an invalid parent index was specified. 358 */ 359 public final RevCommit getParent(int nth) { 360 return parents[nth]; 361 } 362 363 /** 364 * Obtain an array of all parents (<b>NOTE - THIS IS NOT A COPY</b>). 365 * <p> 366 * This method is exposed only to provide very fast, efficient access to 367 * this commit's parent list. Applications relying on this list should be 368 * very careful to ensure they do not modify its contents during their use 369 * of it. 370 * 371 * @return the array of parents. 372 */ 373 public final RevCommit[] getParents() { 374 return parents; 375 } 376 377 /** 378 * Obtain the raw unparsed commit body (<b>NOTE - THIS IS NOT A COPY</b>). 379 * <p> 380 * This method is exposed only to provide very fast, efficient access to 381 * this commit's message buffer within a RevFilter. Applications relying on 382 * this buffer should be very careful to ensure they do not modify its 383 * contents during their use of it. 384 * 385 * @return the raw unparsed commit body. This is <b>NOT A COPY</b>. 386 * Altering the contents of this buffer may alter the walker's 387 * knowledge of this commit, and the results it produces. 388 */ 389 public final byte[] getRawBuffer() { 390 return buffer; 391 } 392 393 /** 394 * Parse the gpg signature from the raw buffer. 395 * <p> 396 * This method parses and returns the raw content of the gpgsig lines. This 397 * method is fairly expensive and produces a new byte[] instance on each 398 * invocation. Callers should invoke this method only if they are certain 399 * they will need, and should cache the return value for as long as 400 * necessary to use all information from it. 401 * <p> 402 * RevFilter implementations should try to use 403 * {@link org.eclipse.jgit.util.RawParseUtils} to scan the 404 * {@link #getRawBuffer()} instead, as this will allow faster evaluation of 405 * commits. 406 * 407 * @return contents of the gpg signature; null if the commit was not signed. 408 * @since 5.1 409 */ 410 public final byte[] getRawGpgSignature() { 411 final byte[] raw = buffer; 412 final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'}; 413 final int start = RawParseUtils.headerStart(header, raw, 0); 414 if (start < 0) { 415 return null; 416 } 417 final int end = RawParseUtils.headerEnd(raw, start); 418 return Arrays.copyOfRange(raw, start, end); 419 } 420 421 /** 422 * Parse the author identity from the raw buffer. 423 * <p> 424 * This method parses and returns the content of the author line, after 425 * taking the commit's character set into account and decoding the author 426 * name and email address. This method is fairly expensive and produces a 427 * new PersonIdent instance on each invocation. Callers should invoke this 428 * method only if they are certain they will be outputting the result, and 429 * should cache the return value for as long as necessary to use all 430 * information from it. 431 * <p> 432 * RevFilter implementations should try to use 433 * {@link org.eclipse.jgit.util.RawParseUtils} to scan the 434 * {@link #getRawBuffer()} instead, as this will allow faster evaluation of 435 * commits. 436 * 437 * @return identity of the author (name, email) and the time the commit was 438 * made by the author; null if no author line was found. 439 */ 440 public final PersonIdent getAuthorIdent() { 441 final byte[] raw = buffer; 442 final int nameB = RawParseUtils.author(raw, 0); 443 if (nameB < 0) 444 return null; 445 return RawParseUtils.parsePersonIdent(raw, nameB); 446 } 447 448 /** 449 * Parse the committer identity from the raw buffer. 450 * <p> 451 * This method parses and returns the content of the committer line, after 452 * taking the commit's character set into account and decoding the committer 453 * name and email address. This method is fairly expensive and produces a 454 * new PersonIdent instance on each invocation. Callers should invoke this 455 * method only if they are certain they will be outputting the result, and 456 * should cache the return value for as long as necessary to use all 457 * information from it. 458 * <p> 459 * RevFilter implementations should try to use 460 * {@link org.eclipse.jgit.util.RawParseUtils} to scan the 461 * {@link #getRawBuffer()} instead, as this will allow faster evaluation of 462 * commits. 463 * 464 * @return identity of the committer (name, email) and the time the commit 465 * was made by the committer; null if no committer line was found. 466 */ 467 public final PersonIdent getCommitterIdent() { 468 final byte[] raw = buffer; 469 final int nameB = RawParseUtils.committer(raw, 0); 470 if (nameB < 0) 471 return null; 472 return RawParseUtils.parsePersonIdent(raw, nameB); 473 } 474 475 /** 476 * Parse the complete commit message and decode it to a string. 477 * <p> 478 * This method parses and returns the message portion of the commit buffer, 479 * after taking the commit's character set into account and decoding the 480 * buffer using that character set. This method is a fairly expensive 481 * operation and produces a new string on each invocation. 482 * 483 * @return decoded commit message as a string. Never null. 484 */ 485 public final String getFullMessage() { 486 byte[] raw = buffer; 487 int msgB = RawParseUtils.commitMessage(raw, 0); 488 if (msgB < 0) { 489 return ""; //$NON-NLS-1$ 490 } 491 return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); 492 } 493 494 /** 495 * Parse the commit message and return the first "line" of it. 496 * <p> 497 * The first line is everything up to the first pair of LFs. This is the 498 * "oneline" format, suitable for output in a single line display. 499 * <p> 500 * This method parses and returns the message portion of the commit buffer, 501 * after taking the commit's character set into account and decoding the 502 * buffer using that character set. This method is a fairly expensive 503 * operation and produces a new string on each invocation. 504 * 505 * @return decoded commit message as a string. Never null. The returned 506 * string does not contain any LFs, even if the first paragraph 507 * spanned multiple lines. Embedded LFs are converted to spaces. 508 */ 509 public final String getShortMessage() { 510 byte[] raw = buffer; 511 int msgB = RawParseUtils.commitMessage(raw, 0); 512 if (msgB < 0) { 513 return ""; //$NON-NLS-1$ 514 } 515 516 int msgE = RawParseUtils.endOfParagraph(raw, msgB); 517 String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); 518 if (hasLF(raw, msgB, msgE)) { 519 str = StringUtils.replaceLineBreaksWithSpace(str); 520 } 521 return str; 522 } 523 524 static boolean hasLF(byte[] r, int b, int e) { 525 while (b < e) 526 if (r[b++] == '\n') 527 return true; 528 return false; 529 } 530 531 /** 532 * Determine the encoding of the commit message buffer. 533 * <p> 534 * Locates the "encoding" header (if present) and returns its value. Due to 535 * corruption in the wild this may be an invalid encoding name that is not 536 * recognized by any character encoding library. 537 * <p> 538 * If no encoding header is present, null. 539 * 540 * @return the preferred encoding of {@link #getRawBuffer()}; or null. 541 * @since 4.2 542 */ 543 @Nullable 544 public final String getEncodingName() { 545 return RawParseUtils.parseEncodingName(buffer); 546 } 547 548 /** 549 * Determine the encoding of the commit message buffer. 550 * <p> 551 * Locates the "encoding" header (if present) and then returns the proper 552 * character set to apply to this buffer to evaluate its contents as 553 * character data. 554 * <p> 555 * If no encoding header is present {@code UTF-8} is assumed. 556 * 557 * @return the preferred encoding of {@link #getRawBuffer()}. 558 * @throws IllegalCharsetNameException 559 * if the character set requested by the encoding header is 560 * malformed and unsupportable. 561 * @throws UnsupportedCharsetException 562 * if the JRE does not support the character set requested by 563 * the encoding header. 564 */ 565 public final Charset getEncoding() { 566 return RawParseUtils.parseEncoding(buffer); 567 } 568 569 private Charset guessEncoding() { 570 try { 571 return getEncoding(); 572 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { 573 return UTF_8; 574 } 575 } 576 577 /** 578 * Parse the footer lines (e.g. "Signed-off-by") for machine processing. 579 * <p> 580 * This method splits all of the footer lines out of the last paragraph of 581 * the commit message, providing each line as a key-value pair, ordered by 582 * the order of the line's appearance in the commit message itself. 583 * <p> 584 * A footer line's key must match the pattern {@code ^[A-Za-z0-9-]+:}, while 585 * the value is free-form, but must not contain an LF. Very common keys seen 586 * in the wild are: 587 * <ul> 588 * <li>{@code Signed-off-by} (agrees to Developer Certificate of Origin) 589 * <li>{@code Acked-by} (thinks change looks sane in context) 590 * <li>{@code Reported-by} (originally found the issue this change fixes) 591 * <li>{@code Tested-by} (validated change fixes the issue for them) 592 * <li>{@code CC}, {@code Cc} (copy on all email related to this change) 593 * <li>{@code Bug} (link to project's bug tracking system) 594 * </ul> 595 * 596 * @return ordered list of footer lines; empty list if no footers found. 597 */ 598 public final List<FooterLine> getFooterLines() { 599 final byte[] raw = buffer; 600 int ptr = raw.length - 1; 601 while (raw[ptr] == '\n') // trim any trailing LFs, not interesting 602 ptr--; 603 604 final int msgB = RawParseUtils.commitMessage(raw, 0); 605 final ArrayList<FooterLine> r = new ArrayList<>(4); 606 final Charset enc = guessEncoding(); 607 for (;;) { 608 ptr = RawParseUtils.prevLF(raw, ptr); 609 if (ptr <= msgB) 610 break; // Don't parse commit headers as footer lines. 611 612 final int keyStart = ptr + 2; 613 if (raw[keyStart] == '\n') 614 break; // Stop at first paragraph break, no footers above it. 615 616 final int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart); 617 if (keyEnd < 0) 618 continue; // Not a well formed footer line, skip it. 619 620 // Skip over the ': *' at the end of the key before the value. 621 // 622 int valStart = keyEnd + 1; 623 while (valStart < raw.length && raw[valStart] == ' ') 624 valStart++; 625 626 // Value ends at the LF, and does not include it. 627 // 628 int valEnd = RawParseUtils.nextLF(raw, valStart); 629 if (raw[valEnd - 1] == '\n') 630 valEnd--; 631 632 r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd)); 633 } 634 Collections.reverse(r); 635 return r; 636 } 637 638 /** 639 * Get the values of all footer lines with the given key. 640 * 641 * @param keyName 642 * footer key to find values of, case insensitive. 643 * @return values of footers with key of {@code keyName}, ordered by their 644 * order of appearance. Duplicates may be returned if the same 645 * footer appeared more than once. Empty list if no footers appear 646 * with the specified key, or there are no footers at all. 647 * @see #getFooterLines() 648 */ 649 public final List<String> getFooterLines(String keyName) { 650 return getFooterLines(new FooterKey(keyName)); 651 } 652 653 /** 654 * Get the values of all footer lines with the given key. 655 * 656 * @param keyName 657 * footer key to find values of, case insensitive. 658 * @return values of footers with key of {@code keyName}, ordered by their 659 * order of appearance. Duplicates may be returned if the same 660 * footer appeared more than once. Empty list if no footers appear 661 * with the specified key, or there are no footers at all. 662 * @see #getFooterLines() 663 */ 664 public final List<String> getFooterLines(FooterKey keyName) { 665 final List<FooterLine> src = getFooterLines(); 666 if (src.isEmpty()) 667 return Collections.emptyList(); 668 final ArrayList<String> r = new ArrayList<>(src.size()); 669 for (FooterLine f : src) { 670 if (f.matches(keyName)) 671 r.add(f.getValue()); 672 } 673 return r; 674 } 675 676 /** 677 * Reset this commit to allow another RevWalk with the same instances. 678 * <p> 679 * Subclasses <b>must</b> call <code>super.reset()</code> to ensure the 680 * basic information can be correctly cleared out. 681 */ 682 public void reset() { 683 inDegree = 0; 684 } 685 686 /** 687 * Discard the message buffer to reduce memory usage. 688 * <p> 689 * After discarding the memory usage of the {@code RevCommit} is reduced to 690 * only the {@link #getTree()} and {@link #getParents()} pointers and the 691 * time in {@link #getCommitTime()}. Accessing other properties such as 692 * {@link #getAuthorIdent()}, {@link #getCommitterIdent()} or either message 693 * function requires reloading the buffer by invoking 694 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. 695 * 696 * @since 4.0 697 */ 698 public final void disposeBody() { 699 buffer = null; 700 } 701 702 /** {@inheritDoc} */ 703 @Override 704 public String toString() { 705 final StringBuilder s = new StringBuilder(); 706 s.append(Constants.typeString(getType())); 707 s.append(' '); 708 s.append(name()); 709 s.append(' '); 710 s.append(commitTime); 711 s.append(' '); 712 appendCoreFlags(s); 713 return s.toString(); 714 } 715 }