TreeFormatter.java

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

  10. package org.eclipse.jgit.lib;

  11. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  12. import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
  13. import static org.eclipse.jgit.lib.Constants.encode;
  14. import static org.eclipse.jgit.lib.FileMode.GITLINK;
  15. import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
  16. import static org.eclipse.jgit.lib.FileMode.TREE;

  17. import java.io.IOException;

  18. import org.eclipse.jgit.errors.CorruptObjectException;
  19. import org.eclipse.jgit.internal.JGitText;
  20. import org.eclipse.jgit.revwalk.RevBlob;
  21. import org.eclipse.jgit.revwalk.RevCommit;
  22. import org.eclipse.jgit.revwalk.RevTree;
  23. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  24. import org.eclipse.jgit.util.TemporaryBuffer;

  25. /**
  26.  * Mutable formatter to construct a single tree object.
  27.  *
  28.  * This formatter does not process subtrees. Callers must handle creating each
  29.  * subtree on their own.
  30.  *
  31.  * To maintain good performance for bulk operations, this formatter does not
  32.  * validate its input. Callers are responsible for ensuring the resulting tree
  33.  * object is correctly well formed by writing entries in the correct order.
  34.  */
  35. public class TreeFormatter {
  36.     /**
  37.      * Compute the size of a tree entry record.
  38.      *
  39.      * This method can be used to estimate the correct size of a tree prior to
  40.      * allocating a formatter. Getting the size correct at allocation time
  41.      * ensures the internal buffer is sized correctly, reducing copying.
  42.      *
  43.      * @param mode
  44.      *            the mode the entry will have.
  45.      * @param nameLen
  46.      *            the length of the name, in bytes.
  47.      * @return the length of the record.
  48.      */
  49.     public static int entrySize(FileMode mode, int nameLen) {
  50.         return mode.copyToLength() + nameLen + OBJECT_ID_LENGTH + 2;
  51.     }

  52.     private byte[] buf;

  53.     private int ptr;

  54.     private TemporaryBuffer.Heap overflowBuffer;

  55.     /**
  56.      * Create an empty formatter with a default buffer size.
  57.      */
  58.     public TreeFormatter() {
  59.         this(8192);
  60.     }

  61.     /**
  62.      * Create an empty formatter with the specified buffer size.
  63.      *
  64.      * @param size
  65.      *            estimated size of the tree, in bytes. Callers can use
  66.      *            {@link #entrySize(FileMode, int)} to estimate the size of each
  67.      *            entry in advance of allocating the formatter.
  68.      */
  69.     public TreeFormatter(int size) {
  70.         buf = new byte[size];
  71.     }

  72.     /**
  73.      * Add a link to a submodule commit, mode is {@link org.eclipse.jgit.lib.FileMode#GITLINK}.
  74.      *
  75.      * @param name
  76.      *            name of the entry.
  77.      * @param commit
  78.      *            the ObjectId to store in this entry.
  79.      */
  80.     public void append(String name, RevCommit commit) {
  81.         append(name, GITLINK, commit);
  82.     }

  83.     /**
  84.      * Add a subtree, mode is {@link org.eclipse.jgit.lib.FileMode#TREE}.
  85.      *
  86.      * @param name
  87.      *            name of the entry.
  88.      * @param tree
  89.      *            the ObjectId to store in this entry.
  90.      */
  91.     public void append(String name, RevTree tree) {
  92.         append(name, TREE, tree);
  93.     }

  94.     /**
  95.      * Add a regular file, mode is {@link org.eclipse.jgit.lib.FileMode#REGULAR_FILE}.
  96.      *
  97.      * @param name
  98.      *            name of the entry.
  99.      * @param blob
  100.      *            the ObjectId to store in this entry.
  101.      */
  102.     public void append(String name, RevBlob blob) {
  103.         append(name, REGULAR_FILE, blob);
  104.     }

  105.     /**
  106.      * Append any entry to the tree.
  107.      *
  108.      * @param name
  109.      *            name of the entry.
  110.      * @param mode
  111.      *            mode describing the treatment of {@code id}.
  112.      * @param id
  113.      *            the ObjectId to store in this entry.
  114.      */
  115.     public void append(String name, FileMode mode, AnyObjectId id) {
  116.         append(encode(name), mode, id);
  117.     }

  118.     /**
  119.      * Append any entry to the tree.
  120.      *
  121.      * @param name
  122.      *            name of the entry. The name should be UTF-8 encoded, but file
  123.      *            name encoding is not a well defined concept in Git.
  124.      * @param mode
  125.      *            mode describing the treatment of {@code id}.
  126.      * @param id
  127.      *            the ObjectId to store in this entry.
  128.      */
  129.     public void append(byte[] name, FileMode mode, AnyObjectId id) {
  130.         append(name, 0, name.length, mode, id);
  131.     }

  132.     /**
  133.      * Append any entry to the tree.
  134.      *
  135.      * @param nameBuf
  136.      *            buffer holding the name of the entry. The name should be UTF-8
  137.      *            encoded, but file name encoding is not a well defined concept
  138.      *            in Git.
  139.      * @param namePos
  140.      *            first position within {@code nameBuf} of the name data.
  141.      * @param nameLen
  142.      *            number of bytes from {@code nameBuf} to use as the name.
  143.      * @param mode
  144.      *            mode describing the treatment of {@code id}.
  145.      * @param id
  146.      *            the ObjectId to store in this entry.
  147.      */
  148.     public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
  149.             AnyObjectId id) {
  150.         append(nameBuf, namePos, nameLen, mode, id, false);
  151.     }

  152.     /**
  153.      * Append any entry to the tree.
  154.      *
  155.      * @param nameBuf
  156.      *            buffer holding the name of the entry. The name should be UTF-8
  157.      *            encoded, but file name encoding is not a well defined concept
  158.      *            in Git.
  159.      * @param namePos
  160.      *            first position within {@code nameBuf} of the name data.
  161.      * @param nameLen
  162.      *            number of bytes from {@code nameBuf} to use as the name.
  163.      * @param mode
  164.      *            mode describing the treatment of {@code id}.
  165.      * @param id
  166.      *            the ObjectId to store in this entry.
  167.      * @param allowEmptyName
  168.      *            allow an empty filename (creating a corrupt tree)
  169.      * @since 4.6
  170.      */
  171.     public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
  172.             AnyObjectId id, boolean allowEmptyName) {
  173.         if (nameLen == 0 && !allowEmptyName) {
  174.             throw new IllegalArgumentException(
  175.                     JGitText.get().invalidTreeZeroLengthName);
  176.         }
  177.         if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
  178.             id.copyRawTo(buf, ptr);
  179.             ptr += OBJECT_ID_LENGTH;

  180.         } else {
  181.             try {
  182.                 fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
  183.                 id.copyRawTo(overflowBuffer);
  184.             } catch (IOException badBuffer) {
  185.                 // This should never occur.
  186.                 throw new RuntimeException(badBuffer);
  187.             }
  188.         }
  189.     }

  190.     /**
  191.      * Append any entry to the tree.
  192.      *
  193.      * @param nameBuf
  194.      *            buffer holding the name of the entry. The name should be UTF-8
  195.      *            encoded, but file name encoding is not a well defined concept
  196.      *            in Git.
  197.      * @param namePos
  198.      *            first position within {@code nameBuf} of the name data.
  199.      * @param nameLen
  200.      *            number of bytes from {@code nameBuf} to use as the name.
  201.      * @param mode
  202.      *            mode describing the treatment of {@code id}.
  203.      * @param idBuf
  204.      *            buffer holding the raw ObjectId of the entry.
  205.      * @param idPos
  206.      *            first position within {@code idBuf} to copy the id from.
  207.      */
  208.     public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
  209.             byte[] idBuf, int idPos) {
  210.         if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
  211.             System.arraycopy(idBuf, idPos, buf, ptr, OBJECT_ID_LENGTH);
  212.             ptr += OBJECT_ID_LENGTH;

  213.         } else {
  214.             try {
  215.                 fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
  216.                 overflowBuffer.write(idBuf, idPos, OBJECT_ID_LENGTH);
  217.             } catch (IOException badBuffer) {
  218.                 // This should never occur.
  219.                 throw new RuntimeException(badBuffer);
  220.             }
  221.         }
  222.     }

  223.     private boolean fmtBuf(byte[] nameBuf, int namePos, int nameLen,
  224.             FileMode mode) {
  225.         if (buf == null || buf.length < ptr + entrySize(mode, nameLen))
  226.             return false;

  227.         mode.copyTo(buf, ptr);
  228.         ptr += mode.copyToLength();
  229.         buf[ptr++] = ' ';

  230.         System.arraycopy(nameBuf, namePos, buf, ptr, nameLen);
  231.         ptr += nameLen;
  232.         buf[ptr++] = 0;
  233.         return true;
  234.     }

  235.     private void fmtOverflowBuffer(byte[] nameBuf, int namePos, int nameLen,
  236.             FileMode mode) throws IOException {
  237.         if (buf != null) {
  238.             overflowBuffer = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
  239.             overflowBuffer.write(buf, 0, ptr);
  240.             buf = null;
  241.         }

  242.         mode.copyTo(overflowBuffer);
  243.         overflowBuffer.write((byte) ' ');
  244.         overflowBuffer.write(nameBuf, namePos, nameLen);
  245.         overflowBuffer.write((byte) 0);
  246.     }

  247.     /**
  248.      * Insert this tree and obtain its ObjectId.
  249.      *
  250.      * @param ins
  251.      *            the inserter to store the tree.
  252.      * @return computed ObjectId of the tree
  253.      * @throws java.io.IOException
  254.      *             the tree could not be stored.
  255.      */
  256.     public ObjectId insertTo(ObjectInserter ins) throws IOException {
  257.         if (buf != null)
  258.             return ins.insert(OBJ_TREE, buf, 0, ptr);

  259.         final long len = overflowBuffer.length();
  260.         return ins.insert(OBJ_TREE, len, overflowBuffer.openInputStream());
  261.     }

  262.     /**
  263.      * Compute the ObjectId for this tree
  264.      *
  265.      * @param ins a {@link org.eclipse.jgit.lib.ObjectInserter} object.
  266.      * @return ObjectId for this tree
  267.      */
  268.     public ObjectId computeId(ObjectInserter ins) {
  269.         if (buf != null)
  270.             return ins.idFor(OBJ_TREE, buf, 0, ptr);

  271.         final long len = overflowBuffer.length();
  272.         try {
  273.             return ins.idFor(OBJ_TREE, len, overflowBuffer.openInputStream());
  274.         } catch (IOException e) {
  275.             // this should never happen
  276.             throw new RuntimeException(e);
  277.         }
  278.     }

  279.     /**
  280.      * Copy this formatter's buffer into a byte array.
  281.      *
  282.      * This method is not efficient, as it needs to create a copy of the
  283.      * internal buffer in order to supply an array of the correct size to the
  284.      * caller. If the buffer is just to pass to an ObjectInserter, consider
  285.      * using {@link org.eclipse.jgit.lib.ObjectInserter#insert(TreeFormatter)}
  286.      * instead.
  287.      *
  288.      * @return a copy of this formatter's buffer.
  289.      */
  290.     public byte[] toByteArray() {
  291.         if (buf != null) {
  292.             byte[] r = new byte[ptr];
  293.             System.arraycopy(buf, 0, r, 0, ptr);
  294.             return r;
  295.         }

  296.         try {
  297.             return overflowBuffer.toByteArray();
  298.         } catch (IOException err) {
  299.             // This should never happen, its read failure on a byte array.
  300.             throw new RuntimeException(err);
  301.         }
  302.     }

  303.     /** {@inheritDoc} */
  304.     @SuppressWarnings("nls")
  305.     @Override
  306.     public String toString() {
  307.         byte[] raw = toByteArray();

  308.         CanonicalTreeParser p = new CanonicalTreeParser();
  309.         p.reset(raw);

  310.         StringBuilder r = new StringBuilder();
  311.         r.append("Tree={");
  312.         if (!p.eof()) {
  313.             r.append('\n');
  314.             try {
  315.                 new ObjectChecker().checkTree(raw);
  316.             } catch (CorruptObjectException error) {
  317.                 r.append("*** ERROR: ").append(error.getMessage()).append("\n");
  318.                 r.append('\n');
  319.             }
  320.         }
  321.         while (!p.eof()) {
  322.             final FileMode mode = p.getEntryFileMode();
  323.             r.append(mode);
  324.             r.append(' ');
  325.             r.append(Constants.typeString(mode.getObjectType()));
  326.             r.append(' ');
  327.             r.append(p.getEntryObjectId().name());
  328.             r.append(' ');
  329.             r.append(p.getEntryPathString());
  330.             r.append('\n');
  331.             p.next();
  332.         }
  333.         r.append("}");
  334.         return r.toString();
  335.     }
  336. }