CanonicalTreeParser.java

  1. /*
  2.  * Copyright (C) 2008-2010, 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.treewalk;

  12. import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
  13. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  14. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  15. import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
  16. import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
  17. import static org.eclipse.jgit.lib.Constants.encode;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.util.Arrays;
  21. import java.util.Collections;

  22. import org.eclipse.jgit.attributes.AttributesNode;
  23. import org.eclipse.jgit.attributes.AttributesRule;
  24. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.lib.AnyObjectId;
  27. import org.eclipse.jgit.lib.FileMode;
  28. import org.eclipse.jgit.lib.MutableObjectId;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.ObjectReader;

  31. /**
  32.  * Parses raw Git trees from the canonical semi-text/semi-binary format.
  33.  */
  34. public class CanonicalTreeParser extends AbstractTreeIterator {
  35.     private static final byte[] EMPTY = {};
  36.     private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);

  37.     private byte[] raw;

  38.     /** First offset within {@link #raw} of the prior entry. */
  39.     private int prevPtr;

  40.     /** First offset within {@link #raw} of the current entry's data. */
  41.     private int currPtr;

  42.     /** Offset one past the current entry (first byte of next entry). */
  43.     private int nextPtr;

  44.     /**
  45.      * Create a new parser.
  46.      */
  47.     public CanonicalTreeParser() {
  48.         reset(EMPTY);
  49.     }

  50.     /**
  51.      * Create a new parser for a tree appearing in a subset of a repository.
  52.      *
  53.      * @param prefix
  54.      *            position of this iterator in the repository tree. The value
  55.      *            may be null or the empty array to indicate the prefix is the
  56.      *            root of the repository. A trailing slash ('/') is
  57.      *            automatically appended if the prefix does not end in '/'.
  58.      * @param reader
  59.      *            reader to load the tree data from.
  60.      * @param treeId
  61.      *            identity of the tree being parsed; used only in exception
  62.      *            messages if data corruption is found.
  63.      * @throws MissingObjectException
  64.      *             the object supplied is not available from the repository.
  65.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  66.      *             the object supplied as an argument is not actually a tree and
  67.      *             cannot be parsed as though it were a tree.
  68.      * @throws java.io.IOException
  69.      *             a loose object or pack file could not be read.
  70.      */
  71.     public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
  72.             final AnyObjectId treeId) throws IncorrectObjectTypeException,
  73.             IOException {
  74.         super(prefix);
  75.         reset(reader, treeId);
  76.     }

  77.     private CanonicalTreeParser(CanonicalTreeParser p) {
  78.         super(p);
  79.     }

  80.     /**
  81.      * Get the parent of this tree parser.
  82.      *
  83.      * @return the parent of this tree parser.
  84.      * @deprecated internal use only
  85.      */
  86.     @Deprecated
  87.     public CanonicalTreeParser getParent() {
  88.         return (CanonicalTreeParser) parent;
  89.     }

  90.     /**
  91.      * Reset this parser to walk through the given tree data.
  92.      *
  93.      * @param treeData
  94.      *            the raw tree content.
  95.      */
  96.     public void reset(byte[] treeData) {
  97.         attributesNode = null;
  98.         raw = treeData;
  99.         prevPtr = -1;
  100.         currPtr = 0;
  101.         if (eof())
  102.             nextPtr = 0;
  103.         else
  104.             parseEntry();
  105.     }

  106.     /**
  107.      * Reset this parser to walk through the given tree.
  108.      *
  109.      * @param reader
  110.      *            reader to use during repository access.
  111.      * @param id
  112.      *            identity of the tree being parsed; used only in exception
  113.      *            messages if data corruption is found.
  114.      * @return the root level parser.
  115.      * @throws MissingObjectException
  116.      *             the object supplied is not available from the repository.
  117.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  118.      *             the object supplied as an argument is not actually a tree and
  119.      *             cannot be parsed as though it were a tree.
  120.      * @throws java.io.IOException
  121.      *             a loose object or pack file could not be read.
  122.      */
  123.     public CanonicalTreeParser resetRoot(final ObjectReader reader,
  124.             final AnyObjectId id) throws IncorrectObjectTypeException,
  125.             IOException {
  126.         CanonicalTreeParser p = this;
  127.         while (p.parent != null)
  128.             p = (CanonicalTreeParser) p.parent;
  129.         p.reset(reader, id);
  130.         return p;
  131.     }

  132.     /**
  133.      * Get this iterator, or its parent, if the tree is at eof.
  134.      *
  135.      * @return this iterator, or its parent, if the tree is at eof.
  136.      */
  137.     public CanonicalTreeParser next() {
  138.         CanonicalTreeParser p = this;
  139.         for (;;) {
  140.             if (p.nextPtr == p.raw.length) {
  141.                 // This parser has reached EOF, return to the parent.
  142.                 if (p.parent == null) {
  143.                     p.currPtr = p.nextPtr;
  144.                     return p;
  145.                 }
  146.                 p = (CanonicalTreeParser) p.parent;
  147.                 continue;
  148.             }

  149.             p.prevPtr = p.currPtr;
  150.             p.currPtr = p.nextPtr;
  151.             p.parseEntry();
  152.             return p;
  153.         }
  154.     }

  155.     /**
  156.      * Reset this parser to walk through the given tree.
  157.      *
  158.      * @param reader
  159.      *            reader to use during repository access.
  160.      * @param id
  161.      *            identity of the tree being parsed; used only in exception
  162.      *            messages if data corruption is found.
  163.      * @throws MissingObjectException
  164.      *             the object supplied is not available from the repository.
  165.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  166.      *             the object supplied as an argument is not actually a tree and
  167.      *             cannot be parsed as though it were a tree.
  168.      * @throws java.io.IOException
  169.      *             a loose object or pack file could not be read.
  170.      */
  171.     public void reset(ObjectReader reader, AnyObjectId id)
  172.             throws IncorrectObjectTypeException, IOException {
  173.         reset(reader.open(id, OBJ_TREE).getCachedBytes());
  174.     }

  175.     /** {@inheritDoc} */
  176.     @Override
  177.     public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
  178.             final MutableObjectId idBuffer)
  179.             throws IncorrectObjectTypeException, IOException {
  180.         idBuffer.fromRaw(idBuffer(), idOffset());
  181.         if (!FileMode.TREE.equals(mode)) {
  182.             final ObjectId me = idBuffer.toObjectId();
  183.             throw new IncorrectObjectTypeException(me, TYPE_TREE);
  184.         }
  185.         return createSubtreeIterator0(reader, idBuffer);
  186.     }

  187.     /**
  188.      * Back door to quickly create a subtree iterator for any subtree.
  189.      * <p>
  190.      * Don't use this unless you are ObjectWalk. The method is meant to be
  191.      * called only once the current entry has been identified as a tree and its
  192.      * identity has been converted into an ObjectId.
  193.      *
  194.      * @param reader
  195.      *            reader to load the tree data from.
  196.      * @param id
  197.      *            ObjectId of the tree to open.
  198.      * @return a new parser that walks over the current subtree.
  199.      * @throws java.io.IOException
  200.      *             a loose object or pack file could not be read.
  201.      */
  202.     public final CanonicalTreeParser createSubtreeIterator0(
  203.             final ObjectReader reader, final AnyObjectId id)
  204.             throws IOException {
  205.         final CanonicalTreeParser p = new CanonicalTreeParser(this);
  206.         p.reset(reader, id);
  207.         return p;
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public CanonicalTreeParser createSubtreeIterator(ObjectReader reader)
  212.             throws IncorrectObjectTypeException, IOException {
  213.         return createSubtreeIterator(reader, new MutableObjectId());
  214.     }

  215.     /** {@inheritDoc} */
  216.     @Override
  217.     public boolean hasId() {
  218.         return true;
  219.     }

  220.     /** {@inheritDoc} */
  221.     @Override
  222.     public byte[] idBuffer() {
  223.         return raw;
  224.     }

  225.     /** {@inheritDoc} */
  226.     @Override
  227.     public int idOffset() {
  228.         return nextPtr - OBJECT_ID_LENGTH;
  229.     }

  230.     /** {@inheritDoc} */
  231.     @Override
  232.     public void reset() {
  233.         if (!first())
  234.             reset(raw);
  235.     }

  236.     /** {@inheritDoc} */
  237.     @Override
  238.     public boolean first() {
  239.         return currPtr == 0;
  240.     }

  241.     /** {@inheritDoc} */
  242.     @Override
  243.     public boolean eof() {
  244.         return currPtr == raw.length;
  245.     }

  246.     /** {@inheritDoc} */
  247.     @Override
  248.     public void next(int delta) {
  249.         if (delta == 1) {
  250.             // Moving forward one is the most common case.
  251.             //
  252.             prevPtr = currPtr;
  253.             currPtr = nextPtr;
  254.             if (!eof())
  255.                 parseEntry();
  256.             return;
  257.         }

  258.         // Fast skip over records, then parse the last one.
  259.         //
  260.         final int end = raw.length;
  261.         int ptr = nextPtr;
  262.         while (--delta > 0 && ptr != end) {
  263.             prevPtr = ptr;
  264.             while (raw[ptr] != 0)
  265.                 ptr++;
  266.             ptr += OBJECT_ID_LENGTH + 1;
  267.         }
  268.         if (delta != 0)
  269.             throw new ArrayIndexOutOfBoundsException(delta);
  270.         currPtr = ptr;
  271.         if (!eof())
  272.             parseEntry();
  273.     }

  274.     /** {@inheritDoc} */
  275.     @Override
  276.     public void back(int delta) {
  277.         if (delta == 1 && 0 <= prevPtr) {
  278.             // Moving back one is common in NameTreeWalk, as the average tree
  279.             // won't have D/F type conflicts to study.
  280.             //
  281.             currPtr = prevPtr;
  282.             prevPtr = -1;
  283.             if (!eof())
  284.                 parseEntry();
  285.             return;
  286.         } else if (delta <= 0)
  287.             throw new ArrayIndexOutOfBoundsException(delta);

  288.         // Fast skip through the records, from the beginning of the tree.
  289.         // There is no reliable way to read the tree backwards, so we must
  290.         // parse all over again from the beginning. We hold the last "delta"
  291.         // positions in a buffer, so we can find the correct position later.
  292.         //
  293.         final int[] trace = new int[delta + 1];
  294.         Arrays.fill(trace, -1);
  295.         int ptr = 0;
  296.         while (ptr != currPtr) {
  297.             System.arraycopy(trace, 1, trace, 0, delta);
  298.             trace[delta] = ptr;
  299.             while (raw[ptr] != 0)
  300.                 ptr++;
  301.             ptr += OBJECT_ID_LENGTH + 1;
  302.         }
  303.         if (trace[1] == -1)
  304.             throw new ArrayIndexOutOfBoundsException(delta);
  305.         prevPtr = trace[0];
  306.         currPtr = trace[1];
  307.         parseEntry();
  308.     }

  309.     private void parseEntry() {
  310.         int ptr = currPtr;
  311.         byte c = raw[ptr++];
  312.         int tmp = c - '0';
  313.         for (;;) {
  314.             c = raw[ptr++];
  315.             if (' ' == c)
  316.                 break;
  317.             tmp <<= 3;
  318.             tmp += c - '0';
  319.         }
  320.         mode = tmp;

  321.         tmp = pathOffset;
  322.         for (;; tmp++) {
  323.             c = raw[ptr++];
  324.             if (c == 0) {
  325.                 break;
  326.             }
  327.             if (tmp >= path.length) {
  328.                 growPath(tmp);
  329.             }
  330.             path[tmp] = c;
  331.         }
  332.         pathLen = tmp;
  333.         nextPtr = ptr + OBJECT_ID_LENGTH;
  334.     }

  335.     /**
  336.      * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} for the
  337.      * current entry.
  338.      *
  339.      * @param reader
  340.      *            {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
  341.      *            .gitattributes entry.
  342.      * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
  343.      *         current entry.
  344.      * @throws java.io.IOException
  345.      * @since 4.2
  346.      */
  347.     public AttributesNode getEntryAttributesNode(ObjectReader reader)
  348.             throws IOException {
  349.         if (attributesNode == null) {
  350.             attributesNode = findAttributes(reader);
  351.         }
  352.         return attributesNode.getRules().isEmpty() ? null : attributesNode;
  353.     }

  354.     private AttributesNode findAttributes(ObjectReader reader)
  355.             throws IOException {
  356.         CanonicalTreeParser itr = new CanonicalTreeParser();
  357.         itr.reset(raw);
  358.         if (itr.findFile(ATTRS)) {
  359.             return loadAttributes(reader, itr.getEntryObjectId());
  360.         }
  361.         return noAttributes();
  362.     }

  363.     private static AttributesNode loadAttributes(ObjectReader reader,
  364.             AnyObjectId id) throws IOException {
  365.         AttributesNode r = new AttributesNode();
  366.         try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
  367.             r.parse(in);
  368.         }
  369.         return r.getRules().isEmpty() ? noAttributes() : r;
  370.     }

  371.     private static AttributesNode noAttributes() {
  372.         return new AttributesNode(Collections.<AttributesRule> emptyList());
  373.     }
  374. }