RevCommit.java

  1. /*
  2.  * Copyright (C) 2008-2009, Google Inc.
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.revwalk;

  12. import static java.nio.charset.StandardCharsets.UTF_8;

  13. import java.io.IOException;
  14. import java.nio.charset.Charset;
  15. import java.nio.charset.IllegalCharsetNameException;
  16. import java.nio.charset.UnsupportedCharsetException;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.eclipse.jgit.annotations.Nullable;
  22. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  23. import org.eclipse.jgit.errors.MissingObjectException;
  24. import org.eclipse.jgit.lib.AnyObjectId;
  25. import org.eclipse.jgit.lib.Constants;
  26. import org.eclipse.jgit.lib.MutableObjectId;
  27. import org.eclipse.jgit.lib.ObjectInserter;
  28. import org.eclipse.jgit.lib.ObjectReader;
  29. import org.eclipse.jgit.lib.PersonIdent;
  30. import org.eclipse.jgit.util.RawParseUtils;
  31. import org.eclipse.jgit.util.StringUtils;

  32. /**
  33.  * A commit reference to a commit in the DAG.
  34.  *
  35.  * The state of the RevCommit isn't populated until the commit is parsed. The
  36.  * newly created RevCommit is unparsed and only has an objectId reference. Other
  37.  * states like parents, trees, commit ident, commit message, etc. are
  38.  * populated/available when the commit is parsed.
  39.  */
  40. public class RevCommit extends RevObject {
  41.     private static final int STACK_DEPTH = 500;

  42.     /**
  43.      * Parse a commit from its canonical format.
  44.      *
  45.      * This method constructs a temporary revision pool, parses the commit as
  46.      * supplied, and returns it to the caller. Since the commit was built inside
  47.      * of a private revision pool its parent pointers will be initialized, but
  48.      * will not have their headers loaded.
  49.      *
  50.      * Applications are discouraged from using this API. Callers usually need
  51.      * more than one commit. Use
  52.      * {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)} to
  53.      * obtain a RevCommit from an existing repository.
  54.      *
  55.      * @param raw
  56.      *            the canonical formatted commit to be parsed.
  57.      * @return the parsed commit, in an isolated revision pool that is not
  58.      *         available to the caller.
  59.      */
  60.     public static RevCommit parse(byte[] raw) {
  61.         try {
  62.             return parse(new RevWalk((ObjectReader) null), raw);
  63.         } catch (IOException ex) {
  64.             throw new RuntimeException(ex);
  65.         }
  66.     }

  67.     /**
  68.      * Parse a commit from its canonical format.
  69.      * <p>
  70.      * This method inserts the commit directly into the caller supplied revision
  71.      * pool, making it appear as though the commit exists in the repository,
  72.      * even if it doesn't. The repository under the pool is not affected.
  73.      * <p>
  74.      * The body of the commit (message, author, committer) is always retained in
  75.      * the returned {@code RevCommit}, even if the supplied {@code RevWalk} has
  76.      * been configured with {@code setRetainBody(false)}.
  77.      *
  78.      * @param rw
  79.      *            the revision pool to allocate the commit within. The commit's
  80.      *            tree and parent pointers will be obtained from this pool.
  81.      * @param raw
  82.      *            the canonical formatted commit to be parsed. This buffer will
  83.      *            be retained by the returned {@code RevCommit} and must not be
  84.      *            modified by the caller.
  85.      * @return the parsed commit, in an isolated revision pool that is not
  86.      *         available to the caller.
  87.      * @throws java.io.IOException
  88.      *             in case of RevWalk initialization fails
  89.      */
  90.     public static RevCommit parse(RevWalk rw, byte[] raw) throws IOException {
  91.         try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
  92.             RevCommit r = rw.lookupCommit(fmt.idFor(Constants.OBJ_COMMIT, raw));
  93.             r.parseCanonical(rw, raw);
  94.             r.buffer = raw;
  95.             return r;
  96.         }
  97.     }

  98.     static final RevCommit[] NO_PARENTS = {};

  99.     private RevTree tree;

  100.     /**
  101.      * Avoid accessing this field directly. Use method
  102.      * {@link RevCommit#getParents()} instead. RevCommit does not allow parents
  103.      * to be overridden and altering parent(s) is not supported.
  104.      *
  105.      * @since 6.3
  106.      */
  107.     protected RevCommit[] parents;

  108.     int commitTime; // An int here for performance, overflows in 2038

  109.     int inDegree;

  110.     private byte[] buffer;

  111.     /**
  112.      * Create a new commit reference.
  113.      *
  114.      * @param id
  115.      *            object name for the commit.
  116.      */
  117.     protected RevCommit(AnyObjectId id) {
  118.         super(id);
  119.     }

  120.     @Override
  121.     void parseHeaders(RevWalk walk) throws MissingObjectException,
  122.             IncorrectObjectTypeException, IOException {
  123.         parseCanonical(walk, walk.getCachedBytes(this));
  124.     }

  125.     @Override
  126.     void parseBody(RevWalk walk) throws MissingObjectException,
  127.             IncorrectObjectTypeException, IOException {
  128.         if (buffer == null) {
  129.             buffer = walk.getCachedBytes(this);
  130.             if ((flags & PARSED) == 0)
  131.                 parseCanonical(walk, buffer);
  132.         }
  133.     }

  134.     void parseCanonical(RevWalk walk, byte[] raw) throws IOException {
  135.         if (!walk.shallowCommitsInitialized) {
  136.             walk.initializeShallowCommits(this);
  137.         }

  138.         final MutableObjectId idBuffer = walk.idBuffer;
  139.         idBuffer.fromString(raw, 5);
  140.         tree = walk.lookupTree(idBuffer);

  141.         int ptr = 46;
  142.         if (getParents() == null) {
  143.             RevCommit[] pList = new RevCommit[1];
  144.             int nParents = 0;
  145.             for (;;) {
  146.                 if (raw[ptr] != 'p') {
  147.                     break;
  148.                 }
  149.                 idBuffer.fromString(raw, ptr + 7);
  150.                 final RevCommit p = walk.lookupCommit(idBuffer);
  151.                 switch (nParents) {
  152.                 case 0:
  153.                     pList[nParents++] = p;
  154.                     break;
  155.                 case 1:
  156.                     pList = new RevCommit[] { pList[0], p };
  157.                     nParents = 2;
  158.                     break;
  159.                 default:
  160.                     if (pList.length <= nParents) {
  161.                         RevCommit[] old = pList;
  162.                         pList = new RevCommit[pList.length + 32];
  163.                         System.arraycopy(old, 0, pList, 0, nParents);
  164.                     }
  165.                     pList[nParents++] = p;
  166.                     break;
  167.                 }
  168.                 ptr += 48;
  169.             }
  170.             if (nParents != pList.length) {
  171.                 RevCommit[] old = pList;
  172.                 pList = new RevCommit[nParents];
  173.                 System.arraycopy(old, 0, pList, 0, nParents);
  174.             }
  175.             parents = pList;
  176.         }

  177.         // extract time from "committer "
  178.         ptr = RawParseUtils.committer(raw, ptr);
  179.         if (ptr > 0) {
  180.             ptr = RawParseUtils.nextLF(raw, ptr, '>');

  181.             // In 2038 commitTime will overflow unless it is changed to long.
  182.             commitTime = RawParseUtils.parseBase10(raw, ptr, null);
  183.         }

  184.         if (walk.isRetainBody()) {
  185.             buffer = raw;
  186.         }
  187.         flags |= PARSED;
  188.     }

  189.     /** {@inheritDoc} */
  190.     @Override
  191.     public final int getType() {
  192.         return Constants.OBJ_COMMIT;
  193.     }

  194.     static void carryFlags(RevCommit c, int carry) {
  195.         FIFORevQueue q = carryFlags1(c, carry, 0);
  196.         if (q != null)
  197.             slowCarryFlags(q, carry);
  198.     }

  199.     private static FIFORevQueue carryFlags1(RevCommit c, int carry, int depth) {
  200.         for (;;) {
  201.             RevCommit[] pList = c.getParents();
  202.             if (pList == null || pList.length == 0)
  203.                 return null;
  204.             if (pList.length != 1) {
  205.                 if (depth == STACK_DEPTH)
  206.                     return defer(c);
  207.                 for (int i = 1; i < pList.length; i++) {
  208.                     RevCommit p = pList[i];
  209.                     if ((p.flags & carry) == carry)
  210.                         continue;
  211.                     p.flags |= carry;
  212.                     FIFORevQueue q = carryFlags1(p, carry, depth + 1);
  213.                     if (q != null)
  214.                         return defer(q, carry, pList, i + 1);
  215.                 }
  216.             }

  217.             c = pList[0];
  218.             if ((c.flags & carry) == carry)
  219.                 return null;
  220.             c.flags |= carry;
  221.         }
  222.     }

  223.     private static FIFORevQueue defer(RevCommit c) {
  224.         FIFORevQueue q = new FIFORevQueue();
  225.         q.add(c);
  226.         return q;
  227.     }

  228.     private static FIFORevQueue defer(FIFORevQueue q, int carry,
  229.             RevCommit[] pList, int i) {
  230.         // In normal case the caller will run pList[0] in a tail recursive
  231.         // fashion by updating the variable. However the caller is unwinding
  232.         // the stack and will skip that pList[0] execution step.
  233.         carryOneStep(q, carry, pList[0]);

  234.         // Remaining parents (if any) need to have flags checked and be
  235.         // enqueued if they have ancestors.
  236.         for (; i < pList.length; i++)
  237.             carryOneStep(q, carry, pList[i]);
  238.         return q;
  239.     }

  240.     private static void slowCarryFlags(FIFORevQueue q, int carry) {
  241.         // Commits in q have non-null parent arrays and have set all
  242.         // flags in carry. This loop finishes copying over the graph.
  243.         for (RevCommit c; (c = q.next()) != null;) {
  244.             for (RevCommit p : c.getParents())
  245.                 carryOneStep(q, carry, p);
  246.         }
  247.     }

  248.     private static void carryOneStep(FIFORevQueue q, int carry, RevCommit c) {
  249.         if ((c.flags & carry) != carry) {
  250.             c.flags |= carry;
  251.             if (c.getParents() != null)
  252.                 q.add(c);
  253.         }
  254.     }

  255.     /**
  256.      * Carry a RevFlag set on this commit to its parents.
  257.      * <p>
  258.      * If this commit is parsed, has parents, and has the supplied flag set on
  259.      * it we automatically add it to the parents, grand-parents, and so on until
  260.      * an unparsed commit or a commit with no parents is discovered. This
  261.      * permits applications to force a flag through the history chain when
  262.      * necessary.
  263.      *
  264.      * @param flag
  265.      *            the single flag value to carry back onto parents.
  266.      */
  267.     public void carry(RevFlag flag) {
  268.         final int carry = flags & flag.mask;
  269.         if (carry != 0)
  270.             carryFlags(this, carry);
  271.     }

  272.     /**
  273.      * Time from the "committer " line of the buffer.
  274.      *
  275.      * @return commit time
  276.      */
  277.     public final int getCommitTime() {
  278.         return commitTime;
  279.     }

  280.     /**
  281.      * Get a reference to this commit's tree.
  282.      *
  283.      * @return tree of this commit.
  284.      */
  285.     public final RevTree getTree() {
  286.         return tree;
  287.     }

  288.     /**
  289.      * Get the number of parent commits listed in this commit.
  290.      *
  291.      * @return number of parents; always a positive value but can be 0.
  292.      */
  293.     public int getParentCount() {
  294.         return parents == null ? 0 : parents.length;
  295.     }

  296.     /**
  297.      * Get the nth parent from this commit's parent list.
  298.      *
  299.      * @param nth
  300.      *            parent index to obtain. Must be in the range 0 through
  301.      *            {@link #getParentCount()}-1.
  302.      * @return the specified parent.
  303.      * @throws java.lang.ArrayIndexOutOfBoundsException
  304.      *             an invalid parent index was specified.
  305.      */
  306.     public RevCommit getParent(int nth) {
  307.         return parents[nth];
  308.     }

  309.     /**
  310.      * Obtain an array of all parents (<b>NOTE - THIS IS NOT A COPY</b>).
  311.      * <p>
  312.      * This method is exposed only to provide very fast, efficient access to
  313.      * this commit's parent list. Applications relying on this list should be
  314.      * very careful to ensure they do not modify its contents during their use
  315.      * of it.
  316.      *
  317.      * @return the array of parents.
  318.      */
  319.     public RevCommit[] getParents() {
  320.         return parents;
  321.     }

  322.     /**
  323.      * Obtain the raw unparsed commit body (<b>NOTE - THIS IS NOT A COPY</b>).
  324.      * <p>
  325.      * This method is exposed only to provide very fast, efficient access to
  326.      * this commit's message buffer within a RevFilter. Applications relying on
  327.      * this buffer should be very careful to ensure they do not modify its
  328.      * contents during their use of it.
  329.      *
  330.      * @return the raw unparsed commit body. This is <b>NOT A COPY</b>. Altering
  331.      *         the contents of this buffer may alter the walker's knowledge of
  332.      *         this commit, and the results it produces.
  333.      */
  334.     public final byte[] getRawBuffer() {
  335.         return buffer;
  336.     }

  337.     /**
  338.      * Parse the gpg signature from the raw buffer.
  339.      * <p>
  340.      * This method parses and returns the raw content of the gpgsig lines. This
  341.      * method is fairly expensive and produces a new byte[] instance on each
  342.      * invocation. Callers should invoke this method only if they are certain
  343.      * they will need, and should cache the return value for as long as
  344.      * necessary to use all information from it.
  345.      * <p>
  346.      * RevFilter implementations should try to use
  347.      * {@link org.eclipse.jgit.util.RawParseUtils} to scan the
  348.      * {@link #getRawBuffer()} instead, as this will allow faster evaluation of
  349.      * commits.
  350.      *
  351.      * @return contents of the gpg signature; null if the commit was not signed.
  352.      * @since 5.1
  353.      */
  354.     public final byte[] getRawGpgSignature() {
  355.         final byte[] raw = buffer;
  356.         final byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
  357.         final int start = RawParseUtils.headerStart(header, raw, 0);
  358.         if (start < 0) {
  359.             return null;
  360.         }
  361.         final int end = RawParseUtils.headerEnd(raw, start);
  362.         return Arrays.copyOfRange(raw, start, end);
  363.     }

  364.     /**
  365.      * Parse the author identity from the raw buffer.
  366.      * <p>
  367.      * This method parses and returns the content of the author line, after
  368.      * taking the commit's character set into account and decoding the author
  369.      * name and email address. This method is fairly expensive and produces a
  370.      * new PersonIdent instance on each invocation. Callers should invoke this
  371.      * method only if they are certain they will be outputting the result, and
  372.      * should cache the return value for as long as necessary to use all
  373.      * information from it.
  374.      * <p>
  375.      * RevFilter implementations should try to use
  376.      * {@link org.eclipse.jgit.util.RawParseUtils} to scan the
  377.      * {@link #getRawBuffer()} instead, as this will allow faster evaluation of
  378.      * commits.
  379.      *
  380.      * @return identity of the author (name, email) and the time the commit was
  381.      *         made by the author; null if no author line was found.
  382.      */
  383.     public final PersonIdent getAuthorIdent() {
  384.         final byte[] raw = buffer;
  385.         final int nameB = RawParseUtils.author(raw, 0);
  386.         if (nameB < 0)
  387.             return null;
  388.         return RawParseUtils.parsePersonIdent(raw, nameB);
  389.     }

  390.     /**
  391.      * Parse the committer identity from the raw buffer.
  392.      * <p>
  393.      * This method parses and returns the content of the committer line, after
  394.      * taking the commit's character set into account and decoding the committer
  395.      * name and email address. This method is fairly expensive and produces a
  396.      * new PersonIdent instance on each invocation. Callers should invoke this
  397.      * method only if they are certain they will be outputting the result, and
  398.      * should cache the return value for as long as necessary to use all
  399.      * information from it.
  400.      * <p>
  401.      * RevFilter implementations should try to use
  402.      * {@link org.eclipse.jgit.util.RawParseUtils} to scan the
  403.      * {@link #getRawBuffer()} instead, as this will allow faster evaluation of
  404.      * commits.
  405.      *
  406.      * @return identity of the committer (name, email) and the time the commit
  407.      *         was made by the committer; null if no committer line was found.
  408.      */
  409.     public final PersonIdent getCommitterIdent() {
  410.         final byte[] raw = buffer;
  411.         final int nameB = RawParseUtils.committer(raw, 0);
  412.         if (nameB < 0)
  413.             return null;
  414.         return RawParseUtils.parsePersonIdent(raw, nameB);
  415.     }

  416.     /**
  417.      * Parse the complete commit message and decode it to a string.
  418.      * <p>
  419.      * This method parses and returns the message portion of the commit buffer,
  420.      * after taking the commit's character set into account and decoding the
  421.      * buffer using that character set. This method is a fairly expensive
  422.      * operation and produces a new string on each invocation.
  423.      *
  424.      * @return decoded commit message as a string. Never null.
  425.      */
  426.     public final String getFullMessage() {
  427.         byte[] raw = buffer;
  428.         int msgB = RawParseUtils.commitMessage(raw, 0);
  429.         if (msgB < 0) {
  430.             return ""; //$NON-NLS-1$
  431.         }
  432.         return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
  433.     }

  434.     /**
  435.      * Parse the commit message and return the first "line" of it.
  436.      * <p>
  437.      * The first line is everything up to the first pair of LFs. This is the
  438.      * "oneline" format, suitable for output in a single line display.
  439.      * <p>
  440.      * This method parses and returns the message portion of the commit buffer,
  441.      * after taking the commit's character set into account and decoding the
  442.      * buffer using that character set. This method is a fairly expensive
  443.      * operation and produces a new string on each invocation.
  444.      *
  445.      * @return decoded commit message as a string. Never null. The returned
  446.      *         string does not contain any LFs, even if the first paragraph
  447.      *         spanned multiple lines. Embedded LFs are converted to spaces.
  448.      */
  449.     public final String getShortMessage() {
  450.         byte[] raw = buffer;
  451.         int msgB = RawParseUtils.commitMessage(raw, 0);
  452.         if (msgB < 0) {
  453.             return ""; //$NON-NLS-1$
  454.         }

  455.         int msgE = RawParseUtils.endOfParagraph(raw, msgB);
  456.         String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
  457.         if (hasLF(raw, msgB, msgE)) {
  458.             str = StringUtils.replaceLineBreaksWithSpace(str);
  459.         }
  460.         return str;
  461.     }

  462.     static boolean hasLF(byte[] r, int b, int e) {
  463.         while (b < e)
  464.             if (r[b++] == '\n')
  465.                 return true;
  466.         return false;
  467.     }

  468.     /**
  469.      * Determine the encoding of the commit message buffer.
  470.      * <p>
  471.      * Locates the "encoding" header (if present) and returns its value. Due to
  472.      * corruption in the wild this may be an invalid encoding name that is not
  473.      * recognized by any character encoding library.
  474.      * <p>
  475.      * If no encoding header is present, null.
  476.      *
  477.      * @return the preferred encoding of {@link #getRawBuffer()}; or null.
  478.      * @since 4.2
  479.      */
  480.     @Nullable
  481.     public final String getEncodingName() {
  482.         return RawParseUtils.parseEncodingName(buffer);
  483.     }

  484.     /**
  485.      * Determine the encoding of the commit message buffer.
  486.      * <p>
  487.      * Locates the "encoding" header (if present) and then returns the proper
  488.      * character set to apply to this buffer to evaluate its contents as
  489.      * character data.
  490.      * <p>
  491.      * If no encoding header is present {@code UTF-8} is assumed.
  492.      *
  493.      * @return the preferred encoding of {@link #getRawBuffer()}.
  494.      * @throws IllegalCharsetNameException
  495.      *             if the character set requested by the encoding header is
  496.      *             malformed and unsupportable.
  497.      * @throws UnsupportedCharsetException
  498.      *             if the JRE does not support the character set requested by
  499.      *             the encoding header.
  500.      */
  501.     public final Charset getEncoding() {
  502.         return RawParseUtils.parseEncoding(buffer);
  503.     }

  504.     private Charset guessEncoding() {
  505.         try {
  506.             return getEncoding();
  507.         } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
  508.             return UTF_8;
  509.         }
  510.     }

  511.     /**
  512.      * Parse the footer lines (e.g. "Signed-off-by") for machine processing.
  513.      * <p>
  514.      * This method splits all of the footer lines out of the last paragraph of
  515.      * the commit message, providing each line as a key-value pair, ordered by
  516.      * the order of the line's appearance in the commit message itself.
  517.      * <p>
  518.      * A footer line's key must match the pattern {@code ^[A-Za-z0-9-]+:}, while
  519.      * the value is free-form, but must not contain an LF. Very common keys seen
  520.      * in the wild are:
  521.      * <ul>
  522.      * <li>{@code Signed-off-by} (agrees to Developer Certificate of Origin)
  523.      * <li>{@code Acked-by} (thinks change looks sane in context)
  524.      * <li>{@code Reported-by} (originally found the issue this change fixes)
  525.      * <li>{@code Tested-by} (validated change fixes the issue for them)
  526.      * <li>{@code CC}, {@code Cc} (copy on all email related to this change)
  527.      * <li>{@code Bug} (link to project's bug tracking system)
  528.      * </ul>
  529.      *
  530.      * @return ordered list of footer lines; empty list if no footers found.
  531.      */
  532.     public final List<FooterLine> getFooterLines() {
  533.         final byte[] raw = buffer;
  534.         int ptr = raw.length - 1;
  535.         while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
  536.             ptr--;

  537.         final int msgB = RawParseUtils.commitMessage(raw, 0);
  538.         final ArrayList<FooterLine> r = new ArrayList<>(4);
  539.         final Charset enc = guessEncoding();
  540.         for (;;) {
  541.             ptr = RawParseUtils.prevLF(raw, ptr);
  542.             if (ptr <= msgB)
  543.                 break; // Don't parse commit headers as footer lines.

  544.             final int keyStart = ptr + 2;
  545.             if (raw[keyStart] == '\n')
  546.                 break; // Stop at first paragraph break, no footers above it.

  547.             final int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart);
  548.             if (keyEnd < 0)
  549.                 continue; // Not a well formed footer line, skip it.

  550.             // Skip over the ': *' at the end of the key before the value.
  551.             //
  552.             int valStart = keyEnd + 1;
  553.             while (valStart < raw.length && raw[valStart] == ' ')
  554.                 valStart++;

  555.             // Value ends at the LF, and does not include it.
  556.             //
  557.             int valEnd = RawParseUtils.nextLF(raw, valStart);
  558.             if (raw[valEnd - 1] == '\n')
  559.                 valEnd--;

  560.             r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
  561.         }
  562.         Collections.reverse(r);
  563.         return r;
  564.     }

  565.     /**
  566.      * Get the values of all footer lines with the given key.
  567.      *
  568.      * @param keyName
  569.      *            footer key to find values of, case insensitive.
  570.      * @return values of footers with key of {@code keyName}, ordered by their
  571.      *         order of appearance. Duplicates may be returned if the same
  572.      *         footer appeared more than once. Empty list if no footers appear
  573.      *         with the specified key, or there are no footers at all.
  574.      * @see #getFooterLines()
  575.      */
  576.     public final List<String> getFooterLines(String keyName) {
  577.         return getFooterLines(new FooterKey(keyName));
  578.     }

  579.     /**
  580.      * Get the values of all footer lines with the given key.
  581.      *
  582.      * @param keyName
  583.      *            footer key to find values of, case insensitive.
  584.      * @return values of footers with key of {@code keyName}, ordered by their
  585.      *         order of appearance. Duplicates may be returned if the same
  586.      *         footer appeared more than once. Empty list if no footers appear
  587.      *         with the specified key, or there are no footers at all.
  588.      * @see #getFooterLines()
  589.      */
  590.     public final List<String> getFooterLines(FooterKey keyName) {
  591.         final List<FooterLine> src = getFooterLines();
  592.         if (src.isEmpty())
  593.             return Collections.emptyList();
  594.         final ArrayList<String> r = new ArrayList<>(src.size());
  595.         for (FooterLine f : src) {
  596.             if (f.matches(keyName))
  597.                 r.add(f.getValue());
  598.         }
  599.         return r;
  600.     }

  601.     /**
  602.      * Reset this commit to allow another RevWalk with the same instances.
  603.      * <p>
  604.      * Subclasses <b>must</b> call <code>super.reset()</code> to ensure the
  605.      * basic information can be correctly cleared out.
  606.      */
  607.     public void reset() {
  608.         inDegree = 0;
  609.     }

  610.     /**
  611.      * Discard the message buffer to reduce memory usage.
  612.      * <p>
  613.      * After discarding the memory usage of the {@code RevCommit} is reduced to
  614.      * only the {@link #getTree()} and {@link #getParents()} pointers and the
  615.      * time in {@link #getCommitTime()}. Accessing other properties such as
  616.      * {@link #getAuthorIdent()}, {@link #getCommitterIdent()} or either message
  617.      * function requires reloading the buffer by invoking
  618.      * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}.
  619.      *
  620.      * @since 4.0
  621.      */
  622.     public final void disposeBody() {
  623.         buffer = null;
  624.     }

  625.     /** {@inheritDoc} */
  626.     @Override
  627.     public String toString() {
  628.         final StringBuilder s = new StringBuilder();
  629.         s.append(Constants.typeString(getType()));
  630.         s.append(' ');
  631.         s.append(name());
  632.         s.append(' ');
  633.         s.append(commitTime);
  634.         s.append(' ');
  635.         appendCoreFlags(s);
  636.         return s.toString();
  637.     }
  638. }