FileTreeIterator.java

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

  13. package org.eclipse.jgit.treewalk;

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

  15. import java.io.ByteArrayInputStream;
  16. import java.io.File;
  17. import java.io.FileInputStream;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.time.Instant;

  21. import org.eclipse.jgit.dircache.DirCacheIterator;
  22. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  23. import org.eclipse.jgit.lib.Constants;
  24. import org.eclipse.jgit.lib.FileMode;
  25. import org.eclipse.jgit.lib.ObjectReader;
  26. import org.eclipse.jgit.lib.Repository;
  27. import org.eclipse.jgit.util.FS;

  28. /**
  29.  * Working directory iterator for standard Java IO.
  30.  * <p>
  31.  * This iterator uses the standard <code>java.io</code> package to read the
  32.  * specified working directory as part of a
  33.  * {@link org.eclipse.jgit.treewalk.TreeWalk}.
  34.  */
  35. public class FileTreeIterator extends WorkingTreeIterator {

  36.     /**
  37.      * the starting directory of this Iterator. All entries are located directly
  38.      * in this directory.
  39.      */
  40.     protected final File directory;

  41.     /**
  42.      * the file system abstraction which will be necessary to perform certain
  43.      * file system operations.
  44.      */
  45.     protected final FS fs;

  46.     /**
  47.      * the strategy used to compute the FileMode for a FileEntry. Can be used to
  48.      * control things such as whether to recurse into a directory or create a
  49.      * gitlink.
  50.      *
  51.      * @since 4.3
  52.      */
  53.     protected final FileModeStrategy fileModeStrategy;

  54.     /**
  55.      * Create a new iterator to traverse the work tree and its children.
  56.      *
  57.      * @param repo
  58.      *            the repository whose working tree will be scanned.
  59.      */
  60.     public FileTreeIterator(Repository repo) {
  61.         this(repo,
  62.                 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
  63.                         NoGitlinksStrategy.INSTANCE :
  64.                         DefaultFileModeStrategy.INSTANCE);
  65.     }

  66.     /**
  67.      * Create a new iterator to traverse the work tree and its children.
  68.      *
  69.      * @param repo
  70.      *            the repository whose working tree will be scanned.
  71.      * @param fileModeStrategy
  72.      *            the strategy to use to determine the FileMode for a FileEntry;
  73.      *            controls gitlinks etc.
  74.      * @since 4.3
  75.      */
  76.     public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
  77.         this(repo.getWorkTree(), repo.getFS(),
  78.                 repo.getConfig().get(WorkingTreeOptions.KEY),
  79.                 fileModeStrategy);
  80.         initRootIterator(repo);
  81.     }

  82.     /**
  83.      * Create a new iterator to traverse the given directory and its children.
  84.      *
  85.      * @param root
  86.      *            the starting directory. This directory should correspond to
  87.      *            the root of the repository.
  88.      * @param fs
  89.      *            the file system abstraction which will be necessary to perform
  90.      *            certain file system operations.
  91.      * @param options
  92.      *            working tree options to be used
  93.      */
  94.     public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) {
  95.         this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
  96.     }

  97.     /**
  98.      * Create a new iterator to traverse the given directory and its children.
  99.      *
  100.      * @param root
  101.      *            the starting directory. This directory should correspond to
  102.      *            the root of the repository.
  103.      * @param fs
  104.      *            the file system abstraction which will be necessary to perform
  105.      *            certain file system operations.
  106.      * @param options
  107.      *            working tree options to be used
  108.      * @param fileModeStrategy
  109.      *            the strategy to use to determine the FileMode for a FileEntry;
  110.      *            controls gitlinks etc.
  111.      * @since 4.3
  112.      */
  113.     public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
  114.                             FileModeStrategy fileModeStrategy) {
  115.         super(options);
  116.         directory = root;
  117.         this.fs = fs;
  118.         this.fileModeStrategy = fileModeStrategy;
  119.         init(entries());
  120.     }

  121.     /**
  122.      * Create a new iterator to traverse a subdirectory.
  123.      *
  124.      * @param p
  125.      *            the parent iterator we were created from.
  126.      * @param root
  127.      *            the subdirectory. This should be a directory contained within
  128.      *            the parent directory.
  129.      * @param fs
  130.      *            the file system abstraction which will be necessary to perform
  131.      *            certain file system operations.
  132.      * @since 4.3
  133.      */
  134.     protected FileTreeIterator(final FileTreeIterator p, final File root,
  135.             FS fs) {
  136.         this(p, root, fs, p.fileModeStrategy);
  137.     }

  138.     /**
  139.      * Create a new iterator to traverse a subdirectory, given the specified
  140.      * FileModeStrategy.
  141.      *
  142.      * @param p
  143.      *            the parent iterator we were created from.
  144.      * @param root
  145.      *            the subdirectory. This should be a directory contained within
  146.      *            the parent directory
  147.      * @param fs
  148.      *            the file system abstraction which will be necessary to perform
  149.      *            certain file system operations.
  150.      * @param fileModeStrategy
  151.      *            the strategy to use to determine the FileMode for a given
  152.      *            FileEntry.
  153.      * @since 4.3
  154.      */
  155.     protected FileTreeIterator(final WorkingTreeIterator p, final File root,
  156.             FS fs, FileModeStrategy fileModeStrategy) {
  157.         super(p);
  158.         directory = root;
  159.         this.fs = fs;
  160.         this.fileModeStrategy = fileModeStrategy;
  161.         init(entries());
  162.     }

  163.     /** {@inheritDoc} */
  164.     @Override
  165.     public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
  166.             throws IncorrectObjectTypeException, IOException {
  167.         if (!walksIgnoredDirectories() && isEntryIgnored()) {
  168.             DirCacheIterator iterator = getDirCacheIterator();
  169.             if (iterator == null) {
  170.                 return new EmptyTreeIterator(this);
  171.             }
  172.             // Only enter if we have an associated DirCacheIterator that is
  173.             // at the same entry (which indicates there is some already
  174.             // tracked file underneath this directory). Otherwise the
  175.             // directory is indeed ignored and can be skipped entirely.
  176.         }
  177.         return enterSubtree();
  178.     }


  179.     /**
  180.      * Create a new iterator for the current entry's subtree.
  181.      * <p>
  182.      * The parent reference of the iterator must be <code>this</code>, otherwise
  183.      * the caller would not be able to exit out of the subtree iterator
  184.      * correctly and return to continue walking <code>this</code>.
  185.      *
  186.      * @return a new iterator that walks over the current subtree.
  187.      * @since 5.0
  188.      */
  189.     protected AbstractTreeIterator enterSubtree() {
  190.         return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs,
  191.                 fileModeStrategy);
  192.     }

  193.     private Entry[] entries() {
  194.         return fs.list(directory, fileModeStrategy);
  195.     }

  196.     /**
  197.      * An interface representing the methods used to determine the FileMode for
  198.      * a FileEntry.
  199.      *
  200.      * @since 4.3
  201.      */
  202.     public interface FileModeStrategy {
  203.         /**
  204.          * Compute the FileMode for a given File, based on its attributes.
  205.          *
  206.          * @param f
  207.          *            the file to return a FileMode for
  208.          * @param attributes
  209.          *            the attributes of a file
  210.          * @return a FileMode indicating whether the file is a regular file, a
  211.          *         directory, a gitlink, etc.
  212.          */
  213.         FileMode getMode(File f, FS.Attributes attributes);
  214.     }

  215.     /**
  216.      * A default implementation of a FileModeStrategy; defaults to treating
  217.      * nested .git directories as gitlinks, etc.
  218.      *
  219.      * @since 4.3
  220.      */
  221.     public static class DefaultFileModeStrategy implements FileModeStrategy {
  222.         /**
  223.          * a singleton instance of the default FileModeStrategy
  224.          */
  225.         public static final DefaultFileModeStrategy INSTANCE =
  226.                 new DefaultFileModeStrategy();

  227.         @Override
  228.         public FileMode getMode(File f, FS.Attributes attributes) {
  229.             if (attributes.isSymbolicLink()) {
  230.                 return FileMode.SYMLINK;
  231.             } else if (attributes.isDirectory()) {
  232.                 if (new File(f, Constants.DOT_GIT).exists()) {
  233.                     return FileMode.GITLINK;
  234.                 }
  235.                 return FileMode.TREE;
  236.             } else if (attributes.isExecutable()) {
  237.                 return FileMode.EXECUTABLE_FILE;
  238.             } else {
  239.                 return FileMode.REGULAR_FILE;
  240.             }
  241.         }
  242.     }

  243.     /**
  244.      * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
  245.      * behavior. This is the same as the default FileModeStrategy, except
  246.      * all directories will be treated as directories regardless of whether
  247.      * or not they contain a .git directory or file.
  248.      *
  249.      * @since 4.3
  250.      */
  251.     public static class NoGitlinksStrategy implements FileModeStrategy {

  252.         /**
  253.          * a singleton instance of the default FileModeStrategy
  254.          */
  255.         public static final NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();

  256.         @Override
  257.         public FileMode getMode(File f, FS.Attributes attributes) {
  258.             if (attributes.isSymbolicLink()) {
  259.                 return FileMode.SYMLINK;
  260.             } else if (attributes.isDirectory()) {
  261.                 return FileMode.TREE;
  262.             } else if (attributes.isExecutable()) {
  263.                 return FileMode.EXECUTABLE_FILE;
  264.             } else {
  265.                 return FileMode.REGULAR_FILE;
  266.             }
  267.         }
  268.     }


  269.     /**
  270.      * Wrapper for a standard Java IO file
  271.      */
  272.     public static class FileEntry extends Entry {
  273.         private final FileMode mode;

  274.         private FS.Attributes attributes;

  275.         private FS fs;

  276.         /**
  277.          * Create a new file entry.
  278.          *
  279.          * @param f
  280.          *            file
  281.          * @param fs
  282.          *            file system
  283.          */
  284.         public FileEntry(File f, FS fs) {
  285.             this(f, fs, DefaultFileModeStrategy.INSTANCE);
  286.         }

  287.         /**
  288.          * Create a new file entry given the specified FileModeStrategy
  289.          *
  290.          * @param f
  291.          *            file
  292.          * @param fs
  293.          *            file system
  294.          * @param fileModeStrategy
  295.          *            the strategy to use when determining the FileMode of a
  296.          *            file; controls gitlinks etc.
  297.          *
  298.          * @since 4.3
  299.          */
  300.         public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
  301.             this.fs = fs;
  302.             f = fs.normalize(f);
  303.             attributes = fs.getAttributes(f);
  304.             mode = fileModeStrategy.getMode(f, attributes);
  305.         }

  306.         /**
  307.          * Create a new file entry given the specified FileModeStrategy
  308.          *
  309.          * @param f
  310.          *            file
  311.          * @param fs
  312.          *            file system
  313.          * @param attributes
  314.          *            of the file
  315.          * @param fileModeStrategy
  316.          *            the strategy to use when determining the FileMode of a
  317.          *            file; controls gitlinks etc.
  318.          *
  319.          * @since 5.0
  320.          */
  321.         public FileEntry(File f, FS fs, FS.Attributes attributes,
  322.                 FileModeStrategy fileModeStrategy) {
  323.             this.fs = fs;
  324.             this.attributes = attributes;
  325.             f = fs.normalize(f);
  326.             mode = fileModeStrategy.getMode(f, attributes);
  327.         }

  328.         @Override
  329.         public FileMode getMode() {
  330.             return mode;
  331.         }

  332.         @Override
  333.         public String getName() {
  334.             return attributes.getName();
  335.         }

  336.         @Override
  337.         public long getLength() {
  338.             return attributes.getLength();
  339.         }

  340.         @Override
  341.         @Deprecated
  342.         public long getLastModified() {
  343.             return attributes.getLastModifiedInstant().toEpochMilli();
  344.         }

  345.         /**
  346.          * @since 5.1.9
  347.          */
  348.         @Override
  349.         public Instant getLastModifiedInstant() {
  350.             return attributes.getLastModifiedInstant();
  351.         }

  352.         @Override
  353.         public InputStream openInputStream() throws IOException {
  354.             if (attributes.isSymbolicLink()) {
  355.                 return new ByteArrayInputStream(fs.readSymLink(getFile())
  356.                         .getBytes(UTF_8));
  357.             }
  358.             return new FileInputStream(getFile());
  359.         }

  360.         /**
  361.          * Get the underlying file of this entry.
  362.          *
  363.          * @return the underlying file of this entry
  364.          */
  365.         public File getFile() {
  366.             return attributes.getFile();
  367.         }
  368.     }

  369.     /**
  370.      * <p>Getter for the field <code>directory</code>.</p>
  371.      *
  372.      * @return The root directory of this iterator
  373.      */
  374.     public File getDirectory() {
  375.         return directory;
  376.     }

  377.     /**
  378.      * Get the location of the working file.
  379.      *
  380.      * @return The location of the working file. This is the same as {@code new
  381.      *         File(getDirectory(), getEntryPath())} but may be faster by
  382.      *         reusing an internal File instance.
  383.      */
  384.     public File getEntryFile() {
  385.         return ((FileEntry) current()).getFile();
  386.     }

  387.     /** {@inheritDoc} */
  388.     @Override
  389.     protected byte[] idSubmodule(Entry e) {
  390.         return idSubmodule(getDirectory(), e);
  391.     }

  392.     /** {@inheritDoc} */
  393.     @Override
  394.     protected String readSymlinkTarget(Entry entry) throws IOException {
  395.         return fs.readSymLink(getEntryFile());
  396.     }
  397. }