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 }