FileUtils.java
- /*
- * Copyright (C) 2010, Google Inc.
- * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
- * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.util;
- import static java.nio.charset.StandardCharsets.UTF_8;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InterruptedIOException;
- import java.nio.channels.FileChannel;
- import java.nio.file.AtomicMoveNotSupportedException;
- import java.nio.file.CopyOption;
- import java.nio.file.DirectoryNotEmptyException;
- import java.nio.file.Files;
- import java.nio.file.InvalidPathException;
- import java.nio.file.LinkOption;
- import java.nio.file.NoSuchFileException;
- import java.nio.file.Path;
- import java.nio.file.StandardCopyOption;
- import java.nio.file.StandardOpenOption;
- import java.nio.file.attribute.BasicFileAttributeView;
- import java.nio.file.attribute.BasicFileAttributes;
- import java.nio.file.attribute.FileTime;
- import java.nio.file.attribute.PosixFileAttributeView;
- import java.nio.file.attribute.PosixFileAttributes;
- import java.nio.file.attribute.PosixFilePermission;
- import java.text.MessageFormat;
- import java.text.Normalizer;
- import java.text.Normalizer.Form;
- import java.time.Instant;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Locale;
- import java.util.Random;
- import java.util.regex.Pattern;
- import java.util.stream.Stream;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.util.FS.Attributes;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * File Utilities
- */
- public class FileUtils {
- private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
- private static final Random RNG = new Random();
- /**
- * Option to delete given {@code File}
- */
- public static final int NONE = 0;
- /**
- * Option to recursively delete given {@code File}
- */
- public static final int RECURSIVE = 1;
- /**
- * Option to retry deletion if not successful
- */
- public static final int RETRY = 2;
- /**
- * Option to skip deletion if file doesn't exist
- */
- public static final int SKIP_MISSING = 4;
- /**
- * Option not to throw exceptions when a deletion finally doesn't succeed.
- * @since 2.0
- */
- public static final int IGNORE_ERRORS = 8;
- /**
- * Option to only delete empty directories. This option can be combined with
- * {@link #RECURSIVE}
- *
- * @since 3.0
- */
- public static final int EMPTY_DIRECTORIES_ONLY = 16;
- /**
- * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
- *
- * @param f
- * {@code File} to be converted to {@code Path}
- * @return the path represented by the file
- * @throws java.io.IOException
- * in case the path represented by the file is not valid (
- * {@link java.nio.file.InvalidPathException})
- * @since 4.10
- */
- public static Path toPath(File f) throws IOException {
- try {
- return f.toPath();
- } catch (InvalidPathException ex) {
- throw new IOException(ex);
- }
- }
- /**
- * Delete file or empty folder
- *
- * @param f
- * {@code File} to be deleted
- * @throws java.io.IOException
- * if deletion of {@code f} fails. This may occur if {@code f}
- * didn't exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to delete the same file.
- */
- public static void delete(File f) throws IOException {
- delete(f, NONE);
- }
- /**
- * Delete file or folder
- *
- * @param f
- * {@code File} to be deleted
- * @param options
- * deletion options, {@code RECURSIVE} for recursive deletion of
- * a subtree, {@code RETRY} to retry when deletion failed.
- * Retrying may help if the underlying file system doesn't allow
- * deletion of files being read by another thread.
- * @throws java.io.IOException
- * if deletion of {@code f} fails. This may occur if {@code f}
- * didn't exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to delete the same file.
- * This exception is not thrown when IGNORE_ERRORS is set.
- */
- public static void delete(File f, int options) throws IOException {
- FS fs = FS.DETECTED;
- if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
- return;
- if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
- final File[] items = f.listFiles();
- if (items != null) {
- List<File> files = new ArrayList<>();
- List<File> dirs = new ArrayList<>();
- for (File c : items)
- if (c.isFile())
- files.add(c);
- else
- dirs.add(c);
- // Try to delete files first, otherwise options
- // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
- // directories before aborting, depending on order.
- for (File file : files)
- delete(file, options);
- for (File d : dirs)
- delete(d, options);
- }
- }
- boolean delete = false;
- if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
- if (f.isDirectory()) {
- delete = true;
- } else if ((options & IGNORE_ERRORS) == 0) {
- throw new IOException(MessageFormat.format(
- JGitText.get().deleteFileFailed, f.getAbsolutePath()));
- }
- } else {
- delete = true;
- }
- if (delete) {
- IOException t = null;
- Path p = f.toPath();
- boolean tryAgain;
- do {
- tryAgain = false;
- try {
- Files.delete(p);
- return;
- } catch (NoSuchFileException | FileNotFoundException e) {
- handleDeleteException(f, e, options,
- SKIP_MISSING | IGNORE_ERRORS);
- return;
- } catch (DirectoryNotEmptyException e) {
- handleDeleteException(f, e, options, IGNORE_ERRORS);
- return;
- } catch (IOException e) {
- if (!f.canWrite()) {
- tryAgain = f.setWritable(true);
- }
- if (!tryAgain) {
- t = e;
- }
- }
- } while (tryAgain);
- if ((options & RETRY) != 0) {
- for (int i = 1; i < 10; i++) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException ex) {
- // ignore
- }
- try {
- Files.deleteIfExists(p);
- return;
- } catch (IOException e) {
- t = e;
- }
- }
- }
- handleDeleteException(f, t, options, IGNORE_ERRORS);
- }
- }
- private static void handleDeleteException(File f, IOException e,
- int allOptions, int checkOptions) throws IOException {
- if (e != null && (allOptions & checkOptions) == 0) {
- throw new IOException(MessageFormat.format(
- JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
- }
- }
- /**
- * Rename a file or folder. If the rename fails and if we are running on a
- * filesystem where it makes sense to repeat a failing rename then repeat
- * the rename operation up to 9 times with 100ms sleep time between two
- * calls. Furthermore if the destination exists and is directory hierarchy
- * with only directories in it, the whole directory hierarchy will be
- * deleted. If the target represents a non-empty directory structure, empty
- * subdirectories within that structure may or may not be deleted even if
- * the method fails. Furthermore if the destination exists and is a file
- * then the file will be deleted and then the rename is retried.
- * <p>
- * This operation is <em>not</em> atomic.
- *
- * @see FS#retryFailedLockFileCommit()
- * @param src
- * the old {@code File}
- * @param dst
- * the new {@code File}
- * @throws java.io.IOException
- * if the rename has failed
- * @since 3.0
- */
- public static void rename(File src, File dst)
- throws IOException {
- rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
- }
- /**
- * Rename a file or folder using the passed
- * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
- * running on a filesystem where it makes sense to repeat a failing rename
- * then repeat the rename operation up to 9 times with 100ms sleep time
- * between two calls. Furthermore if the destination exists and is a
- * directory hierarchy with only directories in it, the whole directory
- * hierarchy will be deleted. If the target represents a non-empty directory
- * structure, empty subdirectories within that structure may or may not be
- * deleted even if the method fails. Furthermore if the destination exists
- * and is a file then the file will be replaced if
- * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
- * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
- * rename will be done atomically or fail with an
- * {@link java.nio.file.AtomicMoveNotSupportedException}
- *
- * @param src
- * the old file
- * @param dst
- * the new file
- * @param options
- * options to pass to
- * {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
- * @throws java.nio.file.AtomicMoveNotSupportedException
- * if file cannot be moved as an atomic file system operation
- * @throws java.io.IOException
- * @since 4.1
- */
- public static void rename(final File src, final File dst,
- CopyOption... options)
- throws AtomicMoveNotSupportedException, IOException {
- int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
- while (--attempts >= 0) {
- try {
- Files.move(toPath(src), toPath(dst), options);
- return;
- } catch (AtomicMoveNotSupportedException e) {
- throw e;
- } catch (IOException e) {
- try {
- if (!dst.delete()) {
- delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
- }
- // On *nix there is no try, you do or do not
- Files.move(toPath(src), toPath(dst), options);
- return;
- } catch (IOException e2) {
- // ignore and continue retry
- }
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new IOException(
- MessageFormat.format(JGitText.get().renameFileFailed,
- src.getAbsolutePath(), dst.getAbsolutePath()),
- e);
- }
- }
- throw new IOException(
- MessageFormat.format(JGitText.get().renameFileFailed,
- src.getAbsolutePath(), dst.getAbsolutePath()));
- }
- /**
- * Creates the directory named by this abstract pathname.
- *
- * @param d
- * directory to be created
- * @throws java.io.IOException
- * if creation of {@code d} fails. This may occur if {@code d}
- * did exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to create the same
- * directory.
- */
- public static void mkdir(File d)
- throws IOException {
- mkdir(d, false);
- }
- /**
- * Creates the directory named by this abstract pathname.
- *
- * @param d
- * directory to be created
- * @param skipExisting
- * if {@code true} skip creation of the given directory if it
- * already exists in the file system
- * @throws java.io.IOException
- * if creation of {@code d} fails. This may occur if {@code d}
- * did exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to create the same
- * directory.
- */
- public static void mkdir(File d, boolean skipExisting)
- throws IOException {
- if (!d.mkdir()) {
- if (skipExisting && d.isDirectory())
- return;
- throw new IOException(MessageFormat.format(
- JGitText.get().mkDirFailed, d.getAbsolutePath()));
- }
- }
- /**
- * Creates the directory named by this abstract pathname, including any
- * necessary but nonexistent parent directories. Note that if this operation
- * fails it may have succeeded in creating some of the necessary parent
- * directories.
- *
- * @param d
- * directory to be created
- * @throws java.io.IOException
- * if creation of {@code d} fails. This may occur if {@code d}
- * did exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to create the same
- * directory.
- */
- public static void mkdirs(File d) throws IOException {
- mkdirs(d, false);
- }
- /**
- * Creates the directory named by this abstract pathname, including any
- * necessary but nonexistent parent directories. Note that if this operation
- * fails it may have succeeded in creating some of the necessary parent
- * directories.
- *
- * @param d
- * directory to be created
- * @param skipExisting
- * if {@code true} skip creation of the given directory if it
- * already exists in the file system
- * @throws java.io.IOException
- * if creation of {@code d} fails. This may occur if {@code d}
- * did exist when the method was called. This can therefore
- * cause java.io.IOExceptions during race conditions when
- * multiple concurrent threads all try to create the same
- * directory.
- */
- public static void mkdirs(File d, boolean skipExisting)
- throws IOException {
- if (!d.mkdirs()) {
- if (skipExisting && d.isDirectory())
- return;
- throw new IOException(MessageFormat.format(
- JGitText.get().mkDirsFailed, d.getAbsolutePath()));
- }
- }
- /**
- * Atomically creates a new, empty file named by this abstract pathname if
- * and only if a file with this name does not yet exist. The check for the
- * existence of the file and the creation of the file if it does not exist
- * are a single operation that is atomic with respect to all other
- * filesystem activities that might affect the file.
- * <p>
- * Note: this method should not be used for file-locking, as the resulting
- * protocol cannot be made to work reliably. The
- * {@link java.nio.channels.FileLock} facility should be used instead.
- *
- * @param f
- * the file to be created
- * @throws java.io.IOException
- * if the named file already exists or if an I/O error occurred
- */
- public static void createNewFile(File f) throws IOException {
- if (!f.createNewFile())
- throw new IOException(MessageFormat.format(
- JGitText.get().createNewFileFailed, f));
- }
- /**
- * Create a symbolic link
- *
- * @param path
- * the path of the symbolic link to create
- * @param target
- * the target of the symbolic link
- * @return the path to the symbolic link
- * @throws java.io.IOException
- * @since 4.2
- */
- public static Path createSymLink(File path, String target)
- throws IOException {
- Path nioPath = toPath(path);
- if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
- BasicFileAttributes attrs = Files.readAttributes(nioPath,
- BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
- if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
- delete(path);
- } else {
- delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
- }
- }
- if (SystemReader.getInstance().isWindows()) {
- target = target.replace('/', '\\');
- }
- Path nioTarget = toPath(new File(target));
- return Files.createSymbolicLink(nioPath, nioTarget);
- }
- /**
- * Read target path of the symlink.
- *
- * @param path
- * a {@link java.io.File} object.
- * @return target path of the symlink, or null if it is not a symbolic link
- * @throws java.io.IOException
- * @since 3.0
- */
- public static String readSymLink(File path) throws IOException {
- Path nioPath = toPath(path);
- Path target = Files.readSymbolicLink(nioPath);
- String targetString = target.toString();
- if (SystemReader.getInstance().isWindows()) {
- targetString = targetString.replace('\\', '/');
- } else if (SystemReader.getInstance().isMacOS()) {
- targetString = Normalizer.normalize(targetString, Form.NFC);
- }
- return targetString;
- }
- /**
- * Create a temporary directory.
- *
- * @param prefix
- * prefix string
- * @param suffix
- * suffix string
- * @param dir
- * The parent dir, can be null to use system default temp dir.
- * @return the temp dir created.
- * @throws java.io.IOException
- * @since 3.4
- */
- public static File createTempDir(String prefix, String suffix, File dir)
- throws IOException {
- final int RETRIES = 1; // When something bad happens, retry once.
- for (int i = 0; i < RETRIES; i++) {
- File tmp = File.createTempFile(prefix, suffix, dir);
- if (!tmp.delete())
- continue;
- if (!tmp.mkdir())
- continue;
- return tmp;
- }
- throw new IOException(JGitText.get().cannotCreateTempDir);
- }
- /**
- * Expresses <code>other</code> as a relative file path from
- * <code>base</code>. File-separator and case sensitivity are based on the
- * current file system.
- *
- * See also
- * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
- *
- * @param base
- * Base path
- * @param other
- * Destination path
- * @return Relative path from <code>base</code> to <code>other</code>
- * @since 4.8
- */
- public static String relativizeNativePath(String base, String other) {
- return FS.DETECTED.relativize(base, other);
- }
- /**
- * Expresses <code>other</code> as a relative file path from
- * <code>base</code>. File-separator and case sensitivity are based on Git's
- * internal representation of files (which matches Unix).
- *
- * See also
- * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
- *
- * @param base
- * Base path
- * @param other
- * Destination path
- * @return Relative path from <code>base</code> to <code>other</code>
- * @since 4.8
- */
- public static String relativizeGitPath(String base, String other) {
- return relativizePath(base, other, "/", false); //$NON-NLS-1$
- }
- /**
- * Expresses <code>other</code> as a relative file path from <code>base</code>
- * <p>
- * For example, if called with the two following paths :
- *
- * <pre>
- * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
- * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
- * </pre>
- *
- * This will return "..\\another_project\\pom.xml".
- *
- * <p>
- * <b>Note</b> that this will return the empty String if <code>base</code>
- * and <code>other</code> are equal.
- * </p>
- *
- * @param base
- * The path against which <code>other</code> should be
- * relativized. This will be assumed to denote the path to a
- * folder and not a file.
- * @param other
- * The path that will be made relative to <code>base</code>.
- * @param dirSeparator
- * A string that separates components of the path. In practice, this is "/" or "\\".
- * @param caseSensitive
- * Whether to consider differently-cased directory names as distinct
- * @return A relative path that, when resolved against <code>base</code>,
- * will yield the original <code>other</code>.
- * @since 4.8
- */
- public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
- if (base.equals(other))
- return ""; //$NON-NLS-1$
- final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
- final String[] otherSegments = other.split(Pattern
- .quote(dirSeparator));
- int commonPrefix = 0;
- while (commonPrefix < baseSegments.length
- && commonPrefix < otherSegments.length) {
- if (caseSensitive
- && baseSegments[commonPrefix]
- .equals(otherSegments[commonPrefix]))
- commonPrefix++;
- else if (!caseSensitive
- && baseSegments[commonPrefix]
- .equalsIgnoreCase(otherSegments[commonPrefix]))
- commonPrefix++;
- else
- break;
- }
- final StringBuilder builder = new StringBuilder();
- for (int i = commonPrefix; i < baseSegments.length; i++)
- builder.append("..").append(dirSeparator); //$NON-NLS-1$
- for (int i = commonPrefix; i < otherSegments.length; i++) {
- builder.append(otherSegments[i]);
- if (i < otherSegments.length - 1)
- builder.append(dirSeparator);
- }
- return builder.toString();
- }
- /**
- * Determine if an IOException is a Stale NFS File Handle
- *
- * @param ioe
- * an {@link java.io.IOException} object.
- * @return a boolean true if the IOException is a Stale NFS FIle Handle
- * @since 4.1
- */
- public static boolean isStaleFileHandle(IOException ioe) {
- String msg = ioe.getMessage();
- return msg != null
- && msg.toLowerCase(Locale.ROOT)
- .matches("stale .*file .*handle"); //$NON-NLS-1$
- }
- /**
- * Determine if a throwable or a cause in its causal chain is a Stale NFS
- * File Handle
- *
- * @param throwable
- * a {@link java.lang.Throwable} object.
- * @return a boolean true if the throwable or a cause in its causal chain is
- * a Stale NFS File Handle
- * @since 4.7
- */
- public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
- while (throwable != null) {
- if (throwable instanceof IOException
- && isStaleFileHandle((IOException) throwable)) {
- return true;
- }
- throwable = throwable.getCause();
- }
- return false;
- }
- /**
- * Like a {@link java.util.function.Function} but throwing an
- * {@link Exception}.
- *
- * @param <A>
- * input type
- * @param <B>
- * output type
- * @since 6.2
- */
- @FunctionalInterface
- public interface IOFunction<A, B> {
- /**
- * Performs the function.
- *
- * @param t
- * input to operate on
- * @return the output
- * @throws Exception
- * if a problem occurs
- */
- B apply(A t) throws Exception;
- }
- private static void backOff(long delay, IOException cause)
- throws IOException {
- try {
- Thread.sleep(delay);
- } catch (InterruptedException e) {
- IOException interruption = new InterruptedIOException();
- interruption.initCause(e);
- interruption.addSuppressed(cause);
- Thread.currentThread().interrupt(); // Re-set flag
- throw interruption;
- }
- }
- /**
- * Invokes the given {@link IOFunction}, performing a limited number of
- * re-tries if exceptions occur that indicate either a stale NFS file handle
- * or that indicate that the file may be written concurrently.
- *
- * @param <T>
- * result type
- * @param file
- * to read
- * @param reader
- * for reading the file and creating an instance of {@code T}
- * @return the result of the {@code reader}, or {@code null} if the file
- * does not exist
- * @throws Exception
- * if a problem occurs
- * @since 6.2
- */
- public static <T> T readWithRetries(File file,
- IOFunction<File, ? extends T> reader)
- throws Exception {
- int maxStaleRetries = 5;
- int retries = 0;
- long backoff = 50;
- while (true) {
- try {
- try {
- return reader.apply(file);
- } catch (IOException e) {
- if (FileUtils.isStaleFileHandleInCausalChain(e)
- && retries < maxStaleRetries) {
- if (LOG.isDebugEnabled()) {
- LOG.debug(MessageFormat.format(
- JGitText.get().packedRefsHandleIsStale,
- Integer.valueOf(retries)), e);
- }
- retries++;
- continue;
- }
- throw e;
- }
- } catch (FileNotFoundException noFile) {
- if (!file.isFile()) {
- return null;
- }
- // Probably Windows and some other thread is writing the file
- // concurrently.
- if (backoff > 1000) {
- throw noFile;
- }
- backOff(backoff, noFile);
- backoff *= 2; // 50, 100, 200, 400, 800 ms
- }
- }
- }
- /**
- * @param file
- * @return {@code true} if the passed file is a symbolic link
- */
- static boolean isSymlink(File file) {
- return Files.isSymbolicLink(file.toPath());
- }
- /**
- * @param file
- * @return lastModified attribute for given file, not following symbolic
- * links
- * @throws IOException
- * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
- * FileTime
- */
- @Deprecated
- static long lastModified(File file) throws IOException {
- return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
- .toMillis();
- }
- /**
- * @param path
- * @return lastModified attribute for given file, not following symbolic
- * links
- */
- static Instant lastModifiedInstant(Path path) {
- try {
- return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
- .toInstant();
- } catch (NoSuchFileException e) {
- LOG.debug(
- "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
- path);
- return Instant.EPOCH;
- } catch (IOException e) {
- LOG.error(MessageFormat
- .format(JGitText.get().readLastModifiedFailed, path), e);
- return Instant.ofEpochMilli(path.toFile().lastModified());
- }
- }
- /**
- * Return all the attributes of a file, without following symbolic links.
- *
- * @param file
- * @return {@link BasicFileAttributes} of the file
- * @throws IOException in case of any I/O errors accessing the file
- *
- * @since 4.5.6
- */
- static BasicFileAttributes fileAttributes(File file) throws IOException {
- return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
- }
- /**
- * Set the last modified time of a file system object.
- *
- * @param file
- * @param time
- * @throws IOException
- */
- @Deprecated
- static void setLastModified(File file, long time) throws IOException {
- Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
- }
- /**
- * Set the last modified time of a file system object.
- *
- * @param path
- * @param time
- * @throws IOException
- */
- static void setLastModified(Path path, Instant time)
- throws IOException {
- Files.setLastModifiedTime(path, FileTime.from(time));
- }
- /**
- * @param file
- * @return {@code true} if the given file exists, not following symbolic
- * links
- */
- static boolean exists(File file) {
- return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
- }
- /**
- * @param file
- * @return {@code true} if the given file is hidden
- * @throws IOException
- */
- static boolean isHidden(File file) throws IOException {
- return Files.isHidden(toPath(file));
- }
- /**
- * Set a file hidden (on Windows)
- *
- * @param file
- * a {@link java.io.File} object.
- * @param hidden
- * a boolean.
- * @throws java.io.IOException
- * @since 4.1
- */
- public static void setHidden(File file, boolean hidden) throws IOException {
- Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
- LinkOption.NOFOLLOW_LINKS);
- }
- /**
- * Get file length
- *
- * @param file
- * a {@link java.io.File}.
- * @return length of the given file
- * @throws java.io.IOException
- * @since 4.1
- */
- public static long getLength(File file) throws IOException {
- Path nioPath = toPath(file);
- if (Files.isSymbolicLink(nioPath))
- return Files.readSymbolicLink(nioPath).toString()
- .getBytes(UTF_8).length;
- return Files.size(nioPath);
- }
- /**
- * @param file
- * @return {@code true} if the given file is a directory, not following
- * symbolic links
- */
- static boolean isDirectory(File file) {
- return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
- }
- /**
- * @param file
- * @return {@code true} if the given file is a file, not following symbolic
- * links
- */
- static boolean isFile(File file) {
- return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
- }
- /**
- * Whether the path is a directory with files in it.
- *
- * @param dir
- * directory path
- * @return {@code true} if the given directory path contains files
- * @throws IOException
- * on any I/O errors accessing the path
- *
- * @since 5.11
- */
- public static boolean hasFiles(Path dir) throws IOException {
- try (Stream<Path> stream = Files.list(dir)) {
- return stream.findAny().isPresent();
- }
- }
- /**
- * Whether the given file can be executed.
- *
- * @param file
- * a {@link java.io.File} object.
- * @return {@code true} if the given file can be executed.
- * @since 4.1
- */
- public static boolean canExecute(File file) {
- if (!isFile(file)) {
- return false;
- }
- return Files.isExecutable(file.toPath());
- }
- /**
- * @param fs
- * @param file
- * @return non null attributes object
- */
- static Attributes getFileAttributesBasic(FS fs, File file) {
- try {
- Path nioPath = toPath(file);
- BasicFileAttributes readAttributes = nioPath
- .getFileSystem()
- .provider()
- .getFileAttributeView(nioPath,
- BasicFileAttributeView.class,
- LinkOption.NOFOLLOW_LINKS).readAttributes();
- Attributes attributes = new Attributes(fs, file,
- true,
- readAttributes.isDirectory(),
- fs.supportsExecute() ? file.canExecute() : false,
- readAttributes.isSymbolicLink(),
- readAttributes.isRegularFile(), //
- readAttributes.creationTime().toMillis(), //
- readAttributes.lastModifiedTime().toInstant(),
- readAttributes.isSymbolicLink() ? Constants
- .encode(readSymLink(file)).length
- : readAttributes.size());
- return attributes;
- } catch (IOException e) {
- return new Attributes(file, fs);
- }
- }
- /**
- * Get file system attributes for the given file.
- *
- * @param fs
- * a {@link org.eclipse.jgit.util.FS} object.
- * @param file
- * a {@link java.io.File}.
- * @return file system attributes for the given file.
- * @since 4.1
- */
- public static Attributes getFileAttributesPosix(FS fs, File file) {
- try {
- Path nioPath = toPath(file);
- PosixFileAttributes readAttributes = nioPath
- .getFileSystem()
- .provider()
- .getFileAttributeView(nioPath,
- PosixFileAttributeView.class,
- LinkOption.NOFOLLOW_LINKS).readAttributes();
- Attributes attributes = new Attributes(
- fs,
- file,
- true, //
- readAttributes.isDirectory(), //
- readAttributes.permissions().contains(
- PosixFilePermission.OWNER_EXECUTE),
- readAttributes.isSymbolicLink(),
- readAttributes.isRegularFile(), //
- readAttributes.creationTime().toMillis(), //
- readAttributes.lastModifiedTime().toInstant(),
- readAttributes.size());
- return attributes;
- } catch (IOException e) {
- return new Attributes(file, fs);
- }
- }
- /**
- * NFC normalize a file (on Mac), otherwise do nothing
- *
- * @param file
- * a {@link java.io.File}.
- * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
- * file
- * @since 4.1
- */
- public static File normalize(File file) {
- if (SystemReader.getInstance().isMacOS()) {
- // TODO: Would it be faster to check with isNormalized first
- // assuming normalized paths are much more common
- String normalized = Normalizer.normalize(file.getPath(),
- Normalizer.Form.NFC);
- return new File(normalized);
- }
- return file;
- }
- /**
- * On Mac: get NFC normalized form of given name, otherwise the given name.
- *
- * @param name
- * a {@link java.lang.String} object.
- * @return on Mac: NFC normalized form of given name
- * @since 4.1
- */
- public static String normalize(String name) {
- if (SystemReader.getInstance().isMacOS()) {
- if (name == null)
- return null;
- return Normalizer.normalize(name, Normalizer.Form.NFC);
- }
- return name;
- }
- /**
- * Best-effort variation of {@link java.io.File#getCanonicalFile()}
- * returning the input file if the file cannot be canonicalized instead of
- * throwing {@link java.io.IOException}.
- *
- * @param file
- * to be canonicalized; may be {@code null}
- * @return canonicalized file, or the unchanged input file if
- * canonicalization failed or if {@code file == null}
- * @throws java.lang.SecurityException
- * if {@link java.io.File#getCanonicalFile()} throws one
- * @since 4.2
- */
- public static File canonicalize(File file) {
- if (file == null) {
- return null;
- }
- try {
- return file.getCanonicalFile();
- } catch (IOException e) {
- return file;
- }
- }
- /**
- * Convert a path to String, replacing separators as necessary.
- *
- * @param file
- * a {@link java.io.File}.
- * @return file's path as a String
- * @since 4.10
- */
- public static String pathToString(File file) {
- final String path = file.getPath();
- if (SystemReader.getInstance().isWindows()) {
- return path.replace('\\', '/');
- }
- return path;
- }
- /**
- * Touch the given file
- *
- * @param f
- * the file to touch
- * @throws IOException
- * @since 5.1.8
- */
- public static void touch(Path f) throws IOException {
- try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
- StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
- // touch
- }
- Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
- }
- /**
- * Compute a delay in a {@code min..max} interval with random jitter.
- *
- * @param last
- * amount of delay waited before the last attempt. This is used
- * to seed the next delay interval. Should be 0 if there was no
- * prior delay.
- * @param min
- * shortest amount of allowable delay between attempts.
- * @param max
- * longest amount of allowable delay between attempts.
- * @return new amount of delay to wait before the next attempt.
- *
- * @since 5.6
- */
- public static long delay(long last, long min, long max) {
- long r = Math.max(0, last * 3 - min);
- if (r > 0) {
- int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
- r = RNG.nextInt(c);
- }
- return Math.max(Math.min(min + r, max), min);
- }
- }