FileUtils.java

  1. /*
  2.  * Copyright (C) 2010, Google Inc.
  3.  * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
  4.  * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.util;

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

  14. import java.io.File;
  15. import java.io.FileNotFoundException;
  16. import java.io.IOException;
  17. import java.nio.channels.FileChannel;
  18. import java.nio.file.AtomicMoveNotSupportedException;
  19. import java.nio.file.CopyOption;
  20. import java.nio.file.DirectoryNotEmptyException;
  21. import java.nio.file.Files;
  22. import java.nio.file.InvalidPathException;
  23. import java.nio.file.LinkOption;
  24. import java.nio.file.NoSuchFileException;
  25. import java.nio.file.Path;
  26. import java.nio.file.StandardCopyOption;
  27. import java.nio.file.StandardOpenOption;
  28. import java.nio.file.attribute.BasicFileAttributeView;
  29. import java.nio.file.attribute.BasicFileAttributes;
  30. import java.nio.file.attribute.FileTime;
  31. import java.nio.file.attribute.PosixFileAttributeView;
  32. import java.nio.file.attribute.PosixFileAttributes;
  33. import java.nio.file.attribute.PosixFilePermission;
  34. import java.text.MessageFormat;
  35. import java.text.Normalizer;
  36. import java.text.Normalizer.Form;
  37. import java.time.Instant;
  38. import java.util.ArrayList;
  39. import java.util.List;
  40. import java.util.Locale;
  41. import java.util.Random;
  42. import java.util.regex.Pattern;
  43. import java.util.stream.Stream;

  44. import org.eclipse.jgit.internal.JGitText;
  45. import org.eclipse.jgit.lib.Constants;
  46. import org.eclipse.jgit.util.FS.Attributes;
  47. import org.slf4j.Logger;
  48. import org.slf4j.LoggerFactory;

  49. /**
  50.  * File Utilities
  51.  */
  52. public class FileUtils {
  53.     private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);

  54.     private static final Random RNG = new Random();

  55.     /**
  56.      * Option to delete given {@code File}
  57.      */
  58.     public static final int NONE = 0;

  59.     /**
  60.      * Option to recursively delete given {@code File}
  61.      */
  62.     public static final int RECURSIVE = 1;

  63.     /**
  64.      * Option to retry deletion if not successful
  65.      */
  66.     public static final int RETRY = 2;

  67.     /**
  68.      * Option to skip deletion if file doesn't exist
  69.      */
  70.     public static final int SKIP_MISSING = 4;

  71.     /**
  72.      * Option not to throw exceptions when a deletion finally doesn't succeed.
  73.      * @since 2.0
  74.      */
  75.     public static final int IGNORE_ERRORS = 8;

  76.     /**
  77.      * Option to only delete empty directories. This option can be combined with
  78.      * {@link #RECURSIVE}
  79.      *
  80.      * @since 3.0
  81.      */
  82.     public static final int EMPTY_DIRECTORIES_ONLY = 16;

  83.     /**
  84.      * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
  85.      *
  86.      * @param f
  87.      *            {@code File} to be converted to {@code Path}
  88.      * @return the path represented by the file
  89.      * @throws java.io.IOException
  90.      *             in case the path represented by the file is not valid (
  91.      *             {@link java.nio.file.InvalidPathException})
  92.      * @since 4.10
  93.      */
  94.     public static Path toPath(File f) throws IOException {
  95.         try {
  96.             return f.toPath();
  97.         } catch (InvalidPathException ex) {
  98.             throw new IOException(ex);
  99.         }
  100.     }

  101.     /**
  102.      * Delete file or empty folder
  103.      *
  104.      * @param f
  105.      *            {@code File} to be deleted
  106.      * @throws java.io.IOException
  107.      *             if deletion of {@code f} fails. This may occur if {@code f}
  108.      *             didn't exist when the method was called. This can therefore
  109.      *             cause java.io.IOExceptions during race conditions when
  110.      *             multiple concurrent threads all try to delete the same file.
  111.      */
  112.     public static void delete(File f) throws IOException {
  113.         delete(f, NONE);
  114.     }

  115.     /**
  116.      * Delete file or folder
  117.      *
  118.      * @param f
  119.      *            {@code File} to be deleted
  120.      * @param options
  121.      *            deletion options, {@code RECURSIVE} for recursive deletion of
  122.      *            a subtree, {@code RETRY} to retry when deletion failed.
  123.      *            Retrying may help if the underlying file system doesn't allow
  124.      *            deletion of files being read by another thread.
  125.      * @throws java.io.IOException
  126.      *             if deletion of {@code f} fails. This may occur if {@code f}
  127.      *             didn't exist when the method was called. This can therefore
  128.      *             cause java.io.IOExceptions during race conditions when
  129.      *             multiple concurrent threads all try to delete the same file.
  130.      *             This exception is not thrown when IGNORE_ERRORS is set.
  131.      */
  132.     public static void delete(File f, int options) throws IOException {
  133.         FS fs = FS.DETECTED;
  134.         if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
  135.             return;

  136.         if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
  137.             final File[] items = f.listFiles();
  138.             if (items != null) {
  139.                 List<File> files = new ArrayList<>();
  140.                 List<File> dirs = new ArrayList<>();
  141.                 for (File c : items)
  142.                     if (c.isFile())
  143.                         files.add(c);
  144.                     else
  145.                         dirs.add(c);
  146.                 // Try to delete files first, otherwise options
  147.                 // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
  148.                 // directories before aborting, depending on order.
  149.                 for (File file : files)
  150.                     delete(file, options);
  151.                 for (File d : dirs)
  152.                     delete(d, options);
  153.             }
  154.         }

  155.         boolean delete = false;
  156.         if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
  157.             if (f.isDirectory()) {
  158.                 delete = true;
  159.             } else if ((options & IGNORE_ERRORS) == 0) {
  160.                 throw new IOException(MessageFormat.format(
  161.                         JGitText.get().deleteFileFailed, f.getAbsolutePath()));
  162.             }
  163.         } else {
  164.             delete = true;
  165.         }

  166.         if (delete) {
  167.             IOException t = null;
  168.             Path p = f.toPath();
  169.             boolean tryAgain;
  170.             do {
  171.                 tryAgain = false;
  172.                 try {
  173.                     Files.delete(p);
  174.                     return;
  175.                 } catch (NoSuchFileException | FileNotFoundException e) {
  176.                     handleDeleteException(f, e, options,
  177.                             SKIP_MISSING | IGNORE_ERRORS);
  178.                     return;
  179.                 } catch (DirectoryNotEmptyException e) {
  180.                     handleDeleteException(f, e, options, IGNORE_ERRORS);
  181.                     return;
  182.                 } catch (IOException e) {
  183.                     if (!f.canWrite()) {
  184.                         tryAgain = f.setWritable(true);
  185.                     }
  186.                     if (!tryAgain) {
  187.                         t = e;
  188.                     }
  189.                 }
  190.             } while (tryAgain);

  191.             if ((options & RETRY) != 0) {
  192.                 for (int i = 1; i < 10; i++) {
  193.                     try {
  194.                         Thread.sleep(100);
  195.                     } catch (InterruptedException ex) {
  196.                         // ignore
  197.                     }
  198.                     try {
  199.                         Files.deleteIfExists(p);
  200.                         return;
  201.                     } catch (IOException e) {
  202.                         t = e;
  203.                     }
  204.                 }
  205.             }
  206.             handleDeleteException(f, t, options, IGNORE_ERRORS);
  207.         }
  208.     }

  209.     private static void handleDeleteException(File f, IOException e,
  210.             int allOptions, int checkOptions) throws IOException {
  211.         if (e != null && (allOptions & checkOptions) == 0) {
  212.             throw new IOException(MessageFormat.format(
  213.                     JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
  214.         }
  215.     }

  216.     /**
  217.      * Rename a file or folder. If the rename fails and if we are running on a
  218.      * filesystem where it makes sense to repeat a failing rename then repeat
  219.      * the rename operation up to 9 times with 100ms sleep time between two
  220.      * calls. Furthermore if the destination exists and is directory hierarchy
  221.      * with only directories in it, the whole directory hierarchy will be
  222.      * deleted. If the target represents a non-empty directory structure, empty
  223.      * subdirectories within that structure may or may not be deleted even if
  224.      * the method fails. Furthermore if the destination exists and is a file
  225.      * then the file will be deleted and then the rename is retried.
  226.      * <p>
  227.      * This operation is <em>not</em> atomic.
  228.      *
  229.      * @see FS#retryFailedLockFileCommit()
  230.      * @param src
  231.      *            the old {@code File}
  232.      * @param dst
  233.      *            the new {@code File}
  234.      * @throws java.io.IOException
  235.      *             if the rename has failed
  236.      * @since 3.0
  237.      */
  238.     public static void rename(File src, File dst)
  239.             throws IOException {
  240.         rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
  241.     }

  242.     /**
  243.      * Rename a file or folder using the passed
  244.      * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
  245.      * running on a filesystem where it makes sense to repeat a failing rename
  246.      * then repeat the rename operation up to 9 times with 100ms sleep time
  247.      * between two calls. Furthermore if the destination exists and is a
  248.      * directory hierarchy with only directories in it, the whole directory
  249.      * hierarchy will be deleted. If the target represents a non-empty directory
  250.      * structure, empty subdirectories within that structure may or may not be
  251.      * deleted even if the method fails. Furthermore if the destination exists
  252.      * and is a file then the file will be replaced if
  253.      * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
  254.      * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
  255.      * rename will be done atomically or fail with an
  256.      * {@link java.nio.file.AtomicMoveNotSupportedException}
  257.      *
  258.      * @param src
  259.      *            the old file
  260.      * @param dst
  261.      *            the new file
  262.      * @param options
  263.      *            options to pass to
  264.      *            {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
  265.      * @throws java.nio.file.AtomicMoveNotSupportedException
  266.      *             if file cannot be moved as an atomic file system operation
  267.      * @throws java.io.IOException
  268.      * @since 4.1
  269.      */
  270.     public static void rename(final File src, final File dst,
  271.             CopyOption... options)
  272.                     throws AtomicMoveNotSupportedException, IOException {
  273.         int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
  274.         while (--attempts >= 0) {
  275.             try {
  276.                 Files.move(toPath(src), toPath(dst), options);
  277.                 return;
  278.             } catch (AtomicMoveNotSupportedException e) {
  279.                 throw e;
  280.             } catch (IOException e) {
  281.                 try {
  282.                     if (!dst.delete()) {
  283.                         delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  284.                     }
  285.                     // On *nix there is no try, you do or do not
  286.                     Files.move(toPath(src), toPath(dst), options);
  287.                     return;
  288.                 } catch (IOException e2) {
  289.                     // ignore and continue retry
  290.                 }
  291.             }
  292.             try {
  293.                 Thread.sleep(100);
  294.             } catch (InterruptedException e) {
  295.                 throw new IOException(
  296.                         MessageFormat.format(JGitText.get().renameFileFailed,
  297.                                 src.getAbsolutePath(), dst.getAbsolutePath()),
  298.                         e);
  299.             }
  300.         }
  301.         throw new IOException(
  302.                 MessageFormat.format(JGitText.get().renameFileFailed,
  303.                         src.getAbsolutePath(), dst.getAbsolutePath()));
  304.     }

  305.     /**
  306.      * Creates the directory named by this abstract pathname.
  307.      *
  308.      * @param d
  309.      *            directory to be created
  310.      * @throws java.io.IOException
  311.      *             if creation of {@code d} fails. This may occur if {@code d}
  312.      *             did exist when the method was called. This can therefore
  313.      *             cause java.io.IOExceptions during race conditions when
  314.      *             multiple concurrent threads all try to create the same
  315.      *             directory.
  316.      */
  317.     public static void mkdir(File d)
  318.             throws IOException {
  319.         mkdir(d, false);
  320.     }

  321.     /**
  322.      * Creates the directory named by this abstract pathname.
  323.      *
  324.      * @param d
  325.      *            directory to be created
  326.      * @param skipExisting
  327.      *            if {@code true} skip creation of the given directory if it
  328.      *            already exists in the file system
  329.      * @throws java.io.IOException
  330.      *             if creation of {@code d} fails. This may occur if {@code d}
  331.      *             did exist when the method was called. This can therefore
  332.      *             cause java.io.IOExceptions during race conditions when
  333.      *             multiple concurrent threads all try to create the same
  334.      *             directory.
  335.      */
  336.     public static void mkdir(File d, boolean skipExisting)
  337.             throws IOException {
  338.         if (!d.mkdir()) {
  339.             if (skipExisting && d.isDirectory())
  340.                 return;
  341.             throw new IOException(MessageFormat.format(
  342.                     JGitText.get().mkDirFailed, d.getAbsolutePath()));
  343.         }
  344.     }

  345.     /**
  346.      * Creates the directory named by this abstract pathname, including any
  347.      * necessary but nonexistent parent directories. Note that if this operation
  348.      * fails it may have succeeded in creating some of the necessary parent
  349.      * directories.
  350.      *
  351.      * @param d
  352.      *            directory to be created
  353.      * @throws java.io.IOException
  354.      *             if creation of {@code d} fails. This may occur if {@code d}
  355.      *             did exist when the method was called. This can therefore
  356.      *             cause java.io.IOExceptions during race conditions when
  357.      *             multiple concurrent threads all try to create the same
  358.      *             directory.
  359.      */
  360.     public static void mkdirs(File d) throws IOException {
  361.         mkdirs(d, false);
  362.     }

  363.     /**
  364.      * Creates the directory named by this abstract pathname, including any
  365.      * necessary but nonexistent parent directories. Note that if this operation
  366.      * fails it may have succeeded in creating some of the necessary parent
  367.      * directories.
  368.      *
  369.      * @param d
  370.      *            directory to be created
  371.      * @param skipExisting
  372.      *            if {@code true} skip creation of the given directory if it
  373.      *            already exists in the file system
  374.      * @throws java.io.IOException
  375.      *             if creation of {@code d} fails. This may occur if {@code d}
  376.      *             did exist when the method was called. This can therefore
  377.      *             cause java.io.IOExceptions during race conditions when
  378.      *             multiple concurrent threads all try to create the same
  379.      *             directory.
  380.      */
  381.     public static void mkdirs(File d, boolean skipExisting)
  382.             throws IOException {
  383.         if (!d.mkdirs()) {
  384.             if (skipExisting && d.isDirectory())
  385.                 return;
  386.             throw new IOException(MessageFormat.format(
  387.                     JGitText.get().mkDirsFailed, d.getAbsolutePath()));
  388.         }
  389.     }

  390.     /**
  391.      * Atomically creates a new, empty file named by this abstract pathname if
  392.      * and only if a file with this name does not yet exist. The check for the
  393.      * existence of the file and the creation of the file if it does not exist
  394.      * are a single operation that is atomic with respect to all other
  395.      * filesystem activities that might affect the file.
  396.      * <p>
  397.      * Note: this method should not be used for file-locking, as the resulting
  398.      * protocol cannot be made to work reliably. The
  399.      * {@link java.nio.channels.FileLock} facility should be used instead.
  400.      *
  401.      * @param f
  402.      *            the file to be created
  403.      * @throws java.io.IOException
  404.      *             if the named file already exists or if an I/O error occurred
  405.      */
  406.     public static void createNewFile(File f) throws IOException {
  407.         if (!f.createNewFile())
  408.             throw new IOException(MessageFormat.format(
  409.                     JGitText.get().createNewFileFailed, f));
  410.     }

  411.     /**
  412.      * Create a symbolic link
  413.      *
  414.      * @param path
  415.      *            the path of the symbolic link to create
  416.      * @param target
  417.      *            the target of the symbolic link
  418.      * @return the path to the symbolic link
  419.      * @throws java.io.IOException
  420.      * @since 4.2
  421.      */
  422.     public static Path createSymLink(File path, String target)
  423.             throws IOException {
  424.         Path nioPath = toPath(path);
  425.         if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
  426.             BasicFileAttributes attrs = Files.readAttributes(nioPath,
  427.                     BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  428.             if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
  429.                 delete(path);
  430.             } else {
  431.                 delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  432.             }
  433.         }
  434.         if (SystemReader.getInstance().isWindows()) {
  435.             target = target.replace('/', '\\');
  436.         }
  437.         Path nioTarget = toPath(new File(target));
  438.         return Files.createSymbolicLink(nioPath, nioTarget);
  439.     }

  440.     /**
  441.      * Read target path of the symlink.
  442.      *
  443.      * @param path
  444.      *            a {@link java.io.File} object.
  445.      * @return target path of the symlink, or null if it is not a symbolic link
  446.      * @throws java.io.IOException
  447.      * @since 3.0
  448.      */
  449.     public static String readSymLink(File path) throws IOException {
  450.         Path nioPath = toPath(path);
  451.         Path target = Files.readSymbolicLink(nioPath);
  452.         String targetString = target.toString();
  453.         if (SystemReader.getInstance().isWindows()) {
  454.             targetString = targetString.replace('\\', '/');
  455.         } else if (SystemReader.getInstance().isMacOS()) {
  456.             targetString = Normalizer.normalize(targetString, Form.NFC);
  457.         }
  458.         return targetString;
  459.     }

  460.     /**
  461.      * Create a temporary directory.
  462.      *
  463.      * @param prefix
  464.      *            prefix string
  465.      * @param suffix
  466.      *            suffix string
  467.      * @param dir
  468.      *            The parent dir, can be null to use system default temp dir.
  469.      * @return the temp dir created.
  470.      * @throws java.io.IOException
  471.      * @since 3.4
  472.      */
  473.     public static File createTempDir(String prefix, String suffix, File dir)
  474.             throws IOException {
  475.         final int RETRIES = 1; // When something bad happens, retry once.
  476.         for (int i = 0; i < RETRIES; i++) {
  477.             File tmp = File.createTempFile(prefix, suffix, dir);
  478.             if (!tmp.delete())
  479.                 continue;
  480.             if (!tmp.mkdir())
  481.                 continue;
  482.             return tmp;
  483.         }
  484.         throw new IOException(JGitText.get().cannotCreateTempDir);
  485.     }

  486.     /**
  487.      * Expresses <code>other</code> as a relative file path from
  488.      * <code>base</code>. File-separator and case sensitivity are based on the
  489.      * current file system.
  490.      *
  491.      * See also
  492.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  493.      *
  494.      * @param base
  495.      *            Base path
  496.      * @param other
  497.      *            Destination path
  498.      * @return Relative path from <code>base</code> to <code>other</code>
  499.      * @since 4.8
  500.      */
  501.     public static String relativizeNativePath(String base, String other) {
  502.         return FS.DETECTED.relativize(base, other);
  503.     }

  504.     /**
  505.      * Expresses <code>other</code> as a relative file path from
  506.      * <code>base</code>. File-separator and case sensitivity are based on Git's
  507.      * internal representation of files (which matches Unix).
  508.      *
  509.      * See also
  510.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  511.      *
  512.      * @param base
  513.      *            Base path
  514.      * @param other
  515.      *            Destination path
  516.      * @return Relative path from <code>base</code> to <code>other</code>
  517.      * @since 4.8
  518.      */
  519.     public static String relativizeGitPath(String base, String other) {
  520.         return relativizePath(base, other, "/", false); //$NON-NLS-1$
  521.     }


  522.     /**
  523.      * Expresses <code>other</code> as a relative file path from <code>base</code>
  524.      * <p>
  525.      * For example, if called with the two following paths :
  526.      *
  527.      * <pre>
  528.      * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
  529.      * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
  530.      * </pre>
  531.      *
  532.      * This will return "..\\another_project\\pom.xml".
  533.      *
  534.      * <p>
  535.      * <b>Note</b> that this will return the empty String if <code>base</code>
  536.      * and <code>other</code> are equal.
  537.      * </p>
  538.      *
  539.      * @param base
  540.      *            The path against which <code>other</code> should be
  541.      *            relativized. This will be assumed to denote the path to a
  542.      *            folder and not a file.
  543.      * @param other
  544.      *            The path that will be made relative to <code>base</code>.
  545.      * @param dirSeparator
  546.      *            A string that separates components of the path. In practice, this is "/" or "\\".
  547.      * @param caseSensitive
  548.      *            Whether to consider differently-cased directory names as distinct
  549.      * @return A relative path that, when resolved against <code>base</code>,
  550.      *         will yield the original <code>other</code>.
  551.      * @since 4.8
  552.      */
  553.     public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
  554.         if (base.equals(other))
  555.             return ""; //$NON-NLS-1$

  556.         final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
  557.         final String[] otherSegments = other.split(Pattern
  558.                 .quote(dirSeparator));

  559.         int commonPrefix = 0;
  560.         while (commonPrefix < baseSegments.length
  561.                 && commonPrefix < otherSegments.length) {
  562.             if (caseSensitive
  563.                     && baseSegments[commonPrefix]
  564.                     .equals(otherSegments[commonPrefix]))
  565.                 commonPrefix++;
  566.             else if (!caseSensitive
  567.                     && baseSegments[commonPrefix]
  568.                             .equalsIgnoreCase(otherSegments[commonPrefix]))
  569.                 commonPrefix++;
  570.             else
  571.                 break;
  572.         }

  573.         final StringBuilder builder = new StringBuilder();
  574.         for (int i = commonPrefix; i < baseSegments.length; i++)
  575.             builder.append("..").append(dirSeparator); //$NON-NLS-1$
  576.         for (int i = commonPrefix; i < otherSegments.length; i++) {
  577.             builder.append(otherSegments[i]);
  578.             if (i < otherSegments.length - 1)
  579.                 builder.append(dirSeparator);
  580.         }
  581.         return builder.toString();
  582.     }

  583.     /**
  584.      * Determine if an IOException is a Stale NFS File Handle
  585.      *
  586.      * @param ioe
  587.      *            an {@link java.io.IOException} object.
  588.      * @return a boolean true if the IOException is a Stale NFS FIle Handle
  589.      * @since 4.1
  590.      */
  591.     public static boolean isStaleFileHandle(IOException ioe) {
  592.         String msg = ioe.getMessage();
  593.         return msg != null
  594.                 && msg.toLowerCase(Locale.ROOT)
  595.                         .matches("stale .*file .*handle"); //$NON-NLS-1$
  596.     }

  597.     /**
  598.      * Determine if a throwable or a cause in its causal chain is a Stale NFS
  599.      * File Handle
  600.      *
  601.      * @param throwable
  602.      *            a {@link java.lang.Throwable} object.
  603.      * @return a boolean true if the throwable or a cause in its causal chain is
  604.      *         a Stale NFS File Handle
  605.      * @since 4.7
  606.      */
  607.     public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
  608.         while (throwable != null) {
  609.             if (throwable instanceof IOException
  610.                     && isStaleFileHandle((IOException) throwable)) {
  611.                 return true;
  612.             }
  613.             throwable = throwable.getCause();
  614.         }
  615.         return false;
  616.     }

  617.     /**
  618.      * @param file
  619.      * @return {@code true} if the passed file is a symbolic link
  620.      */
  621.     static boolean isSymlink(File file) {
  622.         return Files.isSymbolicLink(file.toPath());
  623.     }

  624.     /**
  625.      * @param file
  626.      * @return lastModified attribute for given file, not following symbolic
  627.      *         links
  628.      * @throws IOException
  629.      * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
  630.      *             FileTime
  631.      */
  632.     @Deprecated
  633.     static long lastModified(File file) throws IOException {
  634.         return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
  635.                 .toMillis();
  636.     }

  637.     /**
  638.      * @param path
  639.      * @return lastModified attribute for given file, not following symbolic
  640.      *         links
  641.      */
  642.     static Instant lastModifiedInstant(Path path) {
  643.         try {
  644.             return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
  645.                     .toInstant();
  646.         } catch (NoSuchFileException e) {
  647.             LOG.debug(
  648.                     "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
  649.                     path);
  650.             return Instant.EPOCH;
  651.         } catch (IOException e) {
  652.             LOG.error(MessageFormat
  653.                     .format(JGitText.get().readLastModifiedFailed, path), e);
  654.             return Instant.ofEpochMilli(path.toFile().lastModified());
  655.         }
  656.     }

  657.     /**
  658.      * Return all the attributes of a file, without following symbolic links.
  659.      *
  660.      * @param file
  661.      * @return {@link BasicFileAttributes} of the file
  662.      * @throws IOException in case of any I/O errors accessing the file
  663.      *
  664.      * @since 4.5.6
  665.      */
  666.     static BasicFileAttributes fileAttributes(File file) throws IOException {
  667.         return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  668.     }

  669.     /**
  670.      * Set the last modified time of a file system object.
  671.      *
  672.      * @param file
  673.      * @param time
  674.      * @throws IOException
  675.      */
  676.     @Deprecated
  677.     static void setLastModified(File file, long time) throws IOException {
  678.         Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
  679.     }

  680.     /**
  681.      * Set the last modified time of a file system object.
  682.      *
  683.      * @param path
  684.      * @param time
  685.      * @throws IOException
  686.      */
  687.     static void setLastModified(Path path, Instant time)
  688.             throws IOException {
  689.         Files.setLastModifiedTime(path, FileTime.from(time));
  690.     }

  691.     /**
  692.      * @param file
  693.      * @return {@code true} if the given file exists, not following symbolic
  694.      *         links
  695.      */
  696.     static boolean exists(File file) {
  697.         return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  698.     }

  699.     /**
  700.      * @param file
  701.      * @return {@code true} if the given file is hidden
  702.      * @throws IOException
  703.      */
  704.     static boolean isHidden(File file) throws IOException {
  705.         return Files.isHidden(toPath(file));
  706.     }

  707.     /**
  708.      * Set a file hidden (on Windows)
  709.      *
  710.      * @param file
  711.      *            a {@link java.io.File} object.
  712.      * @param hidden
  713.      *            a boolean.
  714.      * @throws java.io.IOException
  715.      * @since 4.1
  716.      */
  717.     public static void setHidden(File file, boolean hidden) throws IOException {
  718.         Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
  719.                 LinkOption.NOFOLLOW_LINKS);
  720.     }

  721.     /**
  722.      * Get file length
  723.      *
  724.      * @param file
  725.      *            a {@link java.io.File}.
  726.      * @return length of the given file
  727.      * @throws java.io.IOException
  728.      * @since 4.1
  729.      */
  730.     public static long getLength(File file) throws IOException {
  731.         Path nioPath = toPath(file);
  732.         if (Files.isSymbolicLink(nioPath))
  733.             return Files.readSymbolicLink(nioPath).toString()
  734.                     .getBytes(UTF_8).length;
  735.         return Files.size(nioPath);
  736.     }

  737.     /**
  738.      * @param file
  739.      * @return {@code true} if the given file is a directory, not following
  740.      *         symbolic links
  741.      */
  742.     static boolean isDirectory(File file) {
  743.         return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  744.     }

  745.     /**
  746.      * @param file
  747.      * @return {@code true} if the given file is a file, not following symbolic
  748.      *         links
  749.      */
  750.     static boolean isFile(File file) {
  751.         return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  752.     }

  753.     /**
  754.      * Whether the path is a directory with files in it.
  755.      *
  756.      * @param dir
  757.      *            directory path
  758.      * @return {@code true} if the given directory path contains files
  759.      * @throws IOException
  760.      *             on any I/O errors accessing the path
  761.      *
  762.      * @since 5.11
  763.      */
  764.     public static boolean hasFiles(Path dir) throws IOException {
  765.         try (Stream<Path> stream = Files.list(dir)) {
  766.             return stream.findAny().isPresent();
  767.         }
  768.     }

  769.     /**
  770.      * Whether the given file can be executed.
  771.      *
  772.      * @param file
  773.      *            a {@link java.io.File} object.
  774.      * @return {@code true} if the given file can be executed.
  775.      * @since 4.1
  776.      */
  777.     public static boolean canExecute(File file) {
  778.         if (!isFile(file)) {
  779.             return false;
  780.         }
  781.         return Files.isExecutable(file.toPath());
  782.     }

  783.     /**
  784.      * @param fs
  785.      * @param file
  786.      * @return non null attributes object
  787.      */
  788.     static Attributes getFileAttributesBasic(FS fs, File file) {
  789.         try {
  790.             Path nioPath = toPath(file);
  791.             BasicFileAttributes readAttributes = nioPath
  792.                     .getFileSystem()
  793.                     .provider()
  794.                     .getFileAttributeView(nioPath,
  795.                             BasicFileAttributeView.class,
  796.                             LinkOption.NOFOLLOW_LINKS).readAttributes();
  797.             Attributes attributes = new Attributes(fs, file,
  798.                     true,
  799.                     readAttributes.isDirectory(),
  800.                     fs.supportsExecute() ? file.canExecute() : false,
  801.                     readAttributes.isSymbolicLink(),
  802.                     readAttributes.isRegularFile(), //
  803.                     readAttributes.creationTime().toMillis(), //
  804.                     readAttributes.lastModifiedTime().toInstant(),
  805.                     readAttributes.isSymbolicLink() ? Constants
  806.                             .encode(readSymLink(file)).length
  807.                             : readAttributes.size());
  808.             return attributes;
  809.         } catch (IOException e) {
  810.             return new Attributes(file, fs);
  811.         }
  812.     }

  813.     /**
  814.      * Get file system attributes for the given file.
  815.      *
  816.      * @param fs
  817.      *            a {@link org.eclipse.jgit.util.FS} object.
  818.      * @param file
  819.      *            a {@link java.io.File}.
  820.      * @return file system attributes for the given file.
  821.      * @since 4.1
  822.      */
  823.     public static Attributes getFileAttributesPosix(FS fs, File file) {
  824.         try {
  825.             Path nioPath = toPath(file);
  826.             PosixFileAttributes readAttributes = nioPath
  827.                     .getFileSystem()
  828.                     .provider()
  829.                     .getFileAttributeView(nioPath,
  830.                             PosixFileAttributeView.class,
  831.                             LinkOption.NOFOLLOW_LINKS).readAttributes();
  832.             Attributes attributes = new Attributes(
  833.                     fs,
  834.                     file,
  835.                     true, //
  836.                     readAttributes.isDirectory(), //
  837.                     readAttributes.permissions().contains(
  838.                             PosixFilePermission.OWNER_EXECUTE),
  839.                     readAttributes.isSymbolicLink(),
  840.                     readAttributes.isRegularFile(), //
  841.                     readAttributes.creationTime().toMillis(), //
  842.                     readAttributes.lastModifiedTime().toInstant(),
  843.                     readAttributes.size());
  844.             return attributes;
  845.         } catch (IOException e) {
  846.             return new Attributes(file, fs);
  847.         }
  848.     }

  849.     /**
  850.      * NFC normalize a file (on Mac), otherwise do nothing
  851.      *
  852.      * @param file
  853.      *            a {@link java.io.File}.
  854.      * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
  855.      *         file
  856.      * @since 4.1
  857.      */
  858.     public static File normalize(File file) {
  859.         if (SystemReader.getInstance().isMacOS()) {
  860.             // TODO: Would it be faster to check with isNormalized first
  861.             // assuming normalized paths are much more common
  862.             String normalized = Normalizer.normalize(file.getPath(),
  863.                     Normalizer.Form.NFC);
  864.             return new File(normalized);
  865.         }
  866.         return file;
  867.     }

  868.     /**
  869.      * On Mac: get NFC normalized form of given name, otherwise the given name.
  870.      *
  871.      * @param name
  872.      *            a {@link java.lang.String} object.
  873.      * @return on Mac: NFC normalized form of given name
  874.      * @since 4.1
  875.      */
  876.     public static String normalize(String name) {
  877.         if (SystemReader.getInstance().isMacOS()) {
  878.             if (name == null)
  879.                 return null;
  880.             return Normalizer.normalize(name, Normalizer.Form.NFC);
  881.         }
  882.         return name;
  883.     }

  884.     /**
  885.      * Best-effort variation of {@link java.io.File#getCanonicalFile()}
  886.      * returning the input file if the file cannot be canonicalized instead of
  887.      * throwing {@link java.io.IOException}.
  888.      *
  889.      * @param file
  890.      *            to be canonicalized; may be {@code null}
  891.      * @return canonicalized file, or the unchanged input file if
  892.      *         canonicalization failed or if {@code file == null}
  893.      * @throws java.lang.SecurityException
  894.      *             if {@link java.io.File#getCanonicalFile()} throws one
  895.      * @since 4.2
  896.      */
  897.     public static File canonicalize(File file) {
  898.         if (file == null) {
  899.             return null;
  900.         }
  901.         try {
  902.             return file.getCanonicalFile();
  903.         } catch (IOException e) {
  904.             return file;
  905.         }
  906.     }

  907.     /**
  908.      * Convert a path to String, replacing separators as necessary.
  909.      *
  910.      * @param file
  911.      *            a {@link java.io.File}.
  912.      * @return file's path as a String
  913.      * @since 4.10
  914.      */
  915.     public static String pathToString(File file) {
  916.         final String path = file.getPath();
  917.         if (SystemReader.getInstance().isWindows()) {
  918.             return path.replace('\\', '/');
  919.         }
  920.         return path;
  921.     }

  922.     /**
  923.      * Touch the given file
  924.      *
  925.      * @param f
  926.      *            the file to touch
  927.      * @throws IOException
  928.      * @since 5.1.8
  929.      */
  930.     public static void touch(Path f) throws IOException {
  931.         try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
  932.                 StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
  933.             // touch
  934.         }
  935.         Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
  936.     }

  937.     /**
  938.      * Compute a delay in a {@code min..max} interval with random jitter.
  939.      *
  940.      * @param last
  941.      *            amount of delay waited before the last attempt. This is used
  942.      *            to seed the next delay interval. Should be 0 if there was no
  943.      *            prior delay.
  944.      * @param min
  945.      *            shortest amount of allowable delay between attempts.
  946.      * @param max
  947.      *            longest amount of allowable delay between attempts.
  948.      * @return new amount of delay to wait before the next attempt.
  949.      *
  950.      * @since 5.6
  951.      */
  952.     public static long delay(long last, long min, long max) {
  953.         long r = Math.max(0, last * 3 - min);
  954.         if (r > 0) {
  955.             int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
  956.             r = RNG.nextInt(c);
  957.         }
  958.         return Math.max(Math.min(min + r, max), min);
  959.     }
  960. }