ContentSource.java

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

  11. import java.io.BufferedInputStream;
  12. import java.io.FileNotFoundException;
  13. import java.io.IOException;
  14. import java.io.InputStream;

  15. import org.eclipse.jgit.errors.LargeObjectException;
  16. import org.eclipse.jgit.errors.MissingObjectException;
  17. import org.eclipse.jgit.lib.Constants;
  18. import org.eclipse.jgit.lib.ObjectId;
  19. import org.eclipse.jgit.lib.ObjectLoader;
  20. import org.eclipse.jgit.lib.ObjectReader;
  21. import org.eclipse.jgit.lib.ObjectStream;
  22. import org.eclipse.jgit.treewalk.TreeWalk;
  23. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  24. import org.eclipse.jgit.treewalk.filter.PathFilter;

  25. /**
  26.  * Supplies the content of a file for
  27.  * {@link org.eclipse.jgit.diff.DiffFormatter}.
  28.  * <p>
  29.  * A content source is not thread-safe. Sources may contain state, including
  30.  * information about the last ObjectLoader they returned. Callers must be
  31.  * careful to ensure there is no more than one ObjectLoader pending on any
  32.  * source, at any time.
  33.  */
  34. public abstract class ContentSource {
  35.     /**
  36.      * Construct a content source for an ObjectReader.
  37.      *
  38.      * @param reader
  39.      *            the reader to obtain blobs from.
  40.      * @return a source wrapping the reader.
  41.      */
  42.     public static ContentSource create(ObjectReader reader) {
  43.         return new ObjectReaderSource(reader);
  44.     }

  45.     /**
  46.      * Construct a content source for a working directory.
  47.      *
  48.      * If the iterator is a {@link org.eclipse.jgit.treewalk.FileTreeIterator}
  49.      * an optimized version is used that doesn't require seeking through a
  50.      * TreeWalk.
  51.      *
  52.      * @param iterator
  53.      *            the iterator to obtain source files through.
  54.      * @return a content source wrapping the iterator.
  55.      */
  56.     public static ContentSource create(WorkingTreeIterator iterator) {
  57.         return new WorkingTreeSource(iterator);
  58.     }

  59.     /**
  60.      * Determine the size of the object.
  61.      *
  62.      * @param path
  63.      *            the path of the file, relative to the root of the repository.
  64.      * @param id
  65.      *            blob id of the file, if known.
  66.      * @return the size in bytes.
  67.      * @throws java.io.IOException
  68.      *             the file cannot be accessed.
  69.      */
  70.     public abstract long size(String path, ObjectId id) throws IOException;

  71.     /**
  72.      * Open the object.
  73.      *
  74.      * @param path
  75.      *            the path of the file, relative to the root of the repository.
  76.      * @param id
  77.      *            blob id of the file, if known.
  78.      * @return a loader that can supply the content of the file. The loader must
  79.      *         be used before another loader can be obtained from this same
  80.      *         source.
  81.      * @throws java.io.IOException
  82.      *             the file cannot be accessed.
  83.      */
  84.     public abstract ObjectLoader open(String path, ObjectId id)
  85.             throws IOException;

  86.     private static class ObjectReaderSource extends ContentSource {
  87.         private final ObjectReader reader;

  88.         ObjectReaderSource(ObjectReader reader) {
  89.             this.reader = reader;
  90.         }

  91.         @Override
  92.         public long size(String path, ObjectId id) throws IOException {
  93.             try {
  94.                 return reader.getObjectSize(id, Constants.OBJ_BLOB);
  95.             } catch (MissingObjectException ignore) {
  96.                 return 0;
  97.             }
  98.         }

  99.         @Override
  100.         public ObjectLoader open(String path, ObjectId id) throws IOException {
  101.             return reader.open(id, Constants.OBJ_BLOB);
  102.         }
  103.     }

  104.     private static class WorkingTreeSource extends ContentSource {
  105.         private final TreeWalk tw;

  106.         private final WorkingTreeIterator iterator;

  107.         private String current;

  108.         WorkingTreeIterator ptr;

  109.         WorkingTreeSource(WorkingTreeIterator iterator) {
  110.             this.tw = new TreeWalk(iterator.getRepository(),
  111.                     (ObjectReader) null);
  112.             this.tw.setRecursive(true);
  113.             this.iterator = iterator;
  114.         }

  115.         @Override
  116.         public long size(String path, ObjectId id) throws IOException {
  117.             seek(path);
  118.             return ptr.getEntryLength();
  119.         }

  120.         @Override
  121.         public ObjectLoader open(String path, ObjectId id) throws IOException {
  122.             seek(path);
  123.             long entrySize = ptr.getEntryContentLength();
  124.             return new ObjectLoader() {
  125.                 @Override
  126.                 public long getSize() {
  127.                     return entrySize;
  128.                 }

  129.                 @Override
  130.                 public int getType() {
  131.                     return ptr.getEntryFileMode().getObjectType();
  132.                 }

  133.                 @Override
  134.                 public ObjectStream openStream() throws MissingObjectException,
  135.                         IOException {
  136.                     long contentLength = entrySize;
  137.                     InputStream in = ptr.openEntryStream();
  138.                     in = new BufferedInputStream(in);
  139.                     return new ObjectStream.Filter(getType(), contentLength, in);
  140.                 }

  141.                 @Override
  142.                 public boolean isLarge() {
  143.                     return true;
  144.                 }

  145.                 @Override
  146.                 public byte[] getCachedBytes() throws LargeObjectException {
  147.                     throw new LargeObjectException();
  148.                 }
  149.             };
  150.         }

  151.         private void seek(String path) throws IOException {
  152.             if (!path.equals(current)) {
  153.                 iterator.reset();
  154.                 // Possibly this iterator had an associated DirCacheIterator,
  155.                 // but we have no access to it and thus don't know about it.
  156.                 // We have to reset this iterator here to work without
  157.                 // DirCacheIterator and to descend always into ignored
  158.                 // directories. Otherwise we might not find tracked files below
  159.                 // ignored folders. Since we're looking only for a single
  160.                 // specific path this is not a performance problem.
  161.                 iterator.setWalkIgnoredDirectories(true);
  162.                 iterator.setDirCacheIterator(null, -1);
  163.                 tw.reset();
  164.                 tw.addTree(iterator);
  165.                 tw.setFilter(PathFilter.create(path));
  166.                 current = path;
  167.                 if (!tw.next())
  168.                     throw new FileNotFoundException(path);
  169.                 ptr = tw.getTree(0, WorkingTreeIterator.class);
  170.                 if (ptr == null)
  171.                     throw new FileNotFoundException(path);
  172.             }
  173.         }
  174.     }

  175.     /** A pair of sources to access the old and new sides of a DiffEntry. */
  176.     public static final class Pair {
  177.         private final ContentSource oldSource;

  178.         private final ContentSource newSource;

  179.         /**
  180.          * Construct a pair of sources.
  181.          *
  182.          * @param oldSource
  183.          *            source to read the old side of a DiffEntry.
  184.          * @param newSource
  185.          *            source to read the new side of a DiffEntry.
  186.          */
  187.         public Pair(ContentSource oldSource, ContentSource newSource) {
  188.             this.oldSource = oldSource;
  189.             this.newSource = newSource;
  190.         }

  191.         /**
  192.          * Determine the size of the object.
  193.          *
  194.          * @param side
  195.          *            which side of the entry to read (OLD or NEW).
  196.          * @param ent
  197.          *            the entry to examine.
  198.          * @return the size in bytes.
  199.          * @throws IOException
  200.          *             the file cannot be accessed.
  201.          */
  202.         public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
  203.             switch (side) {
  204.             case OLD:
  205.                 return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
  206.             case NEW:
  207.                 return newSource.size(ent.newPath, ent.newId.toObjectId());
  208.             default:
  209.                 throw new IllegalArgumentException();
  210.             }
  211.         }

  212.         /**
  213.          * Open the object.
  214.          *
  215.          * @param side
  216.          *            which side of the entry to read (OLD or NEW).
  217.          * @param ent
  218.          *            the entry to examine.
  219.          * @return a loader that can supply the content of the file. The loader
  220.          *         must be used before another loader can be obtained from this
  221.          *         same source.
  222.          * @throws IOException
  223.          *             the file cannot be accessed.
  224.          */
  225.         public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
  226.                 throws IOException {
  227.             switch (side) {
  228.             case OLD:
  229.                 return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
  230.             case NEW:
  231.                 return newSource.open(ent.newPath, ent.newId.toObjectId());
  232.             default:
  233.                 throw new IllegalArgumentException();
  234.             }
  235.         }
  236.     }
  237. }