DirCacheIterator.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.dircache;

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

  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.util.Collections;

  16. import org.eclipse.jgit.attributes.AttributesNode;
  17. import org.eclipse.jgit.attributes.AttributesRule;
  18. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  19. import org.eclipse.jgit.lib.Constants;
  20. import org.eclipse.jgit.lib.FileMode;
  21. import org.eclipse.jgit.lib.ObjectId;
  22. import org.eclipse.jgit.lib.ObjectLoader;
  23. import org.eclipse.jgit.lib.ObjectReader;
  24. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  25. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  26. import org.eclipse.jgit.util.RawParseUtils;

  27. /**
  28.  * Iterate a {@link org.eclipse.jgit.dircache.DirCache} as part of a
  29.  * <code>TreeWalk</code>.
  30.  * <p>
  31.  * This is an iterator to adapt a loaded <code>DirCache</code> instance (such as
  32.  * read from an existing <code>.git/index</code> file) to the tree structure
  33.  * used by a <code>TreeWalk</code>, making it possible for applications to walk
  34.  * over any combination of tree objects already in the object database, index
  35.  * files, or working directories.
  36.  *
  37.  * @see org.eclipse.jgit.treewalk.TreeWalk
  38.  */
  39. public class DirCacheIterator extends AbstractTreeIterator {
  40.     /** Byte array holding ".gitattributes" string */
  41.     private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES
  42.             .getBytes(UTF_8);

  43.     /** The cache this iterator was created to walk. */
  44.     protected final DirCache cache;

  45.     /** The tree this iterator is walking. */
  46.     private final DirCacheTree tree;

  47.     /** First position in this tree. */
  48.     private final int treeStart;

  49.     /** Last position in this tree. */
  50.     private final int treeEnd;

  51.     /** Special buffer to hold the ObjectId of {@link #currentSubtree}. */
  52.     private final byte[] subtreeId;

  53.     /** Index of entry within {@link #cache}. */
  54.     protected int ptr;

  55.     /** Next subtree to consider within {@link #tree}. */
  56.     private int nextSubtreePos;

  57.     /** The current file entry from {@link #cache}. */
  58.     protected DirCacheEntry currentEntry;

  59.     /** The subtree containing {@link #currentEntry} if this is first entry. */
  60.     protected DirCacheTree currentSubtree;

  61.     /**
  62.      * Create a new iterator for an already loaded DirCache instance.
  63.      * <p>
  64.      * The iterator implementation may copy part of the cache's data during
  65.      * construction, so the cache must be read in prior to creating the
  66.      * iterator.
  67.      *
  68.      * @param dc
  69.      *            the cache to walk. It must be already loaded into memory.
  70.      */
  71.     public DirCacheIterator(DirCache dc) {
  72.         cache = dc;
  73.         tree = dc.getCacheTree(true);
  74.         treeStart = 0;
  75.         treeEnd = tree.getEntrySpan();
  76.         subtreeId = new byte[Constants.OBJECT_ID_LENGTH];
  77.         if (!eof())
  78.             parseEntry();
  79.     }

  80.     DirCacheIterator(DirCacheIterator p, DirCacheTree dct) {
  81.         super(p, p.path, p.pathLen + 1);
  82.         cache = p.cache;
  83.         tree = dct;
  84.         treeStart = p.ptr;
  85.         treeEnd = treeStart + tree.getEntrySpan();
  86.         subtreeId = p.subtreeId;
  87.         ptr = p.ptr;
  88.         parseEntry();
  89.     }

  90.     /** {@inheritDoc} */
  91.     @Override
  92.     public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
  93.             throws IncorrectObjectTypeException, IOException {
  94.         if (currentSubtree == null)
  95.             throw new IncorrectObjectTypeException(getEntryObjectId(),
  96.                     Constants.TYPE_TREE);
  97.         return new DirCacheIterator(this, currentSubtree);
  98.     }

  99.     /** {@inheritDoc} */
  100.     @Override
  101.     public EmptyTreeIterator createEmptyTreeIterator() {
  102.         final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
  103.         System.arraycopy(path, 0, n, 0, pathLen);
  104.         n[pathLen] = '/';
  105.         return new EmptyTreeIterator(this, n, pathLen + 1);
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public boolean hasId() {
  110.         if (currentSubtree != null)
  111.             return currentSubtree.isValid();
  112.         return currentEntry != null;
  113.     }

  114.     /** {@inheritDoc} */
  115.     @Override
  116.     public byte[] idBuffer() {
  117.         if (currentSubtree != null)
  118.             return currentSubtree.isValid() ? subtreeId : zeroid;
  119.         if (currentEntry != null)
  120.             return currentEntry.idBuffer();
  121.         return zeroid;
  122.     }

  123.     /** {@inheritDoc} */
  124.     @Override
  125.     public int idOffset() {
  126.         if (currentSubtree != null)
  127.             return 0;
  128.         if (currentEntry != null)
  129.             return currentEntry.idOffset();
  130.         return 0;
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public void reset() {
  135.         if (!first()) {
  136.             ptr = treeStart;
  137.             nextSubtreePos = 0;
  138.             currentEntry = null;
  139.             currentSubtree = null;
  140.             if (!eof())
  141.                 parseEntry();
  142.         }
  143.     }

  144.     /** {@inheritDoc} */
  145.     @Override
  146.     public boolean first() {
  147.         return ptr == treeStart;
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     public boolean eof() {
  152.         return ptr == treeEnd;
  153.     }

  154.     /** {@inheritDoc} */
  155.     @Override
  156.     public void next(int delta) {
  157.         while (--delta >= 0) {
  158.             if (currentSubtree != null)
  159.                 ptr += currentSubtree.getEntrySpan();
  160.             else
  161.                 ptr++;
  162.             if (eof())
  163.                 break;
  164.             parseEntry();
  165.         }
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public void back(int delta) {
  170.         while (--delta >= 0) {
  171.             if (currentSubtree != null)
  172.                 nextSubtreePos--;
  173.             ptr--;
  174.             parseEntry(false);
  175.             if (currentSubtree != null)
  176.                 ptr -= currentSubtree.getEntrySpan() - 1;
  177.         }
  178.     }

  179.     private void parseEntry() {
  180.         parseEntry(true);
  181.     }

  182.     private void parseEntry(boolean forward) {
  183.         currentEntry = cache.getEntry(ptr);
  184.         final byte[] cep = currentEntry.path;

  185.         if (!forward) {
  186.             if (nextSubtreePos > 0) {
  187.                 final DirCacheTree p = tree.getChild(nextSubtreePos - 1);
  188.                 if (p.contains(cep, pathOffset, cep.length)) {
  189.                     nextSubtreePos--;
  190.                     currentSubtree = p;
  191.                 }
  192.             }
  193.         }
  194.         if (nextSubtreePos != tree.getChildCount()) {
  195.             final DirCacheTree s = tree.getChild(nextSubtreePos);
  196.             if (s.contains(cep, pathOffset, cep.length)) {
  197.                 // The current position is the first file of this subtree.
  198.                 // Use the subtree instead as the current position.
  199.                 //
  200.                 currentSubtree = s;
  201.                 nextSubtreePos++;

  202.                 if (s.isValid())
  203.                     s.getObjectId().copyRawTo(subtreeId, 0);
  204.                 mode = FileMode.TREE.getBits();
  205.                 path = cep;
  206.                 pathLen = pathOffset + s.nameLength();
  207.                 return;
  208.             }
  209.         }

  210.         // The current position is a file/symlink/gitlink so we
  211.         // do not have a subtree located here.
  212.         //
  213.         mode = currentEntry.getRawMode();
  214.         path = cep;
  215.         pathLen = cep.length;
  216.         currentSubtree = null;
  217.         // Checks if this entry is a .gitattributes file
  218.         if (RawParseUtils.match(path, pathOffset, DOT_GIT_ATTRIBUTES_BYTES) == path.length)
  219.             attributesNode = new LazyLoadingAttributesNode(
  220.                     currentEntry.getObjectId());
  221.     }

  222.     /**
  223.      * Get the DirCacheEntry for the current file.
  224.      *
  225.      * @return the current cache entry, if this iterator is positioned on a
  226.      *         non-tree.
  227.      */
  228.     public DirCacheEntry getDirCacheEntry() {
  229.         return currentSubtree == null ? currentEntry : null;
  230.     }

  231.     /**
  232.      * Retrieves the {@link org.eclipse.jgit.attributes.AttributesNode} for the
  233.      * current entry.
  234.      *
  235.      * @param reader
  236.      *            {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
  237.      *            .gitattributes entry.
  238.      * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
  239.      *         current entry.
  240.      * @throws java.io.IOException
  241.      * @since 3.7
  242.      */
  243.     public AttributesNode getEntryAttributesNode(ObjectReader reader)
  244.             throws IOException {
  245.         if (attributesNode instanceof LazyLoadingAttributesNode)
  246.             attributesNode = ((LazyLoadingAttributesNode) attributesNode)
  247.                     .load(reader);
  248.         return attributesNode;
  249.     }

  250.     /**
  251.      * {@link AttributesNode} implementation that provides lazy loading
  252.      * facilities.
  253.      */
  254.     private static class LazyLoadingAttributesNode extends AttributesNode {
  255.         final ObjectId objectId;

  256.         LazyLoadingAttributesNode(ObjectId objectId) {
  257.             super(Collections.<AttributesRule> emptyList());
  258.             this.objectId = objectId;

  259.         }

  260.         AttributesNode load(ObjectReader reader) throws IOException {
  261.             AttributesNode r = new AttributesNode();
  262.             ObjectLoader loader = reader.open(objectId);
  263.             if (loader != null) {
  264.                 try (InputStream in = loader.openStream()) {
  265.                     r.parse(in);
  266.                 }
  267.             }
  268.             return r.getRules().isEmpty() ? null : r;
  269.         }
  270.     }

  271. }