View Javadoc
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 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 @Nullable 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 }