View Javadoc
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  
13  package org.eclipse.jgit.util;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  
17  import java.io.File;
18  import java.io.FileNotFoundException;
19  import java.io.IOException;
20  import java.nio.channels.FileChannel;
21  import java.nio.file.AtomicMoveNotSupportedException;
22  import java.nio.file.CopyOption;
23  import java.nio.file.DirectoryNotEmptyException;
24  import java.nio.file.Files;
25  import java.nio.file.InvalidPathException;
26  import java.nio.file.LinkOption;
27  import java.nio.file.NoSuchFileException;
28  import java.nio.file.Path;
29  import java.nio.file.StandardCopyOption;
30  import java.nio.file.StandardOpenOption;
31  import java.nio.file.attribute.BasicFileAttributeView;
32  import java.nio.file.attribute.BasicFileAttributes;
33  import java.nio.file.attribute.FileTime;
34  import java.nio.file.attribute.PosixFileAttributeView;
35  import java.nio.file.attribute.PosixFileAttributes;
36  import java.nio.file.attribute.PosixFilePermission;
37  import java.text.MessageFormat;
38  import java.text.Normalizer;
39  import java.text.Normalizer.Form;
40  import java.time.Instant;
41  import java.util.ArrayList;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.Random;
45  import java.util.regex.Pattern;
46  
47  import org.eclipse.jgit.internal.JGitText;
48  import org.eclipse.jgit.lib.Constants;
49  import org.eclipse.jgit.util.FS.Attributes;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  /**
54   * File Utilities
55   */
56  public class FileUtils {
57  	private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
58  
59  	private static final Random RNG = new Random();
60  
61  	/**
62  	 * Option to delete given {@code File}
63  	 */
64  	public static final int NONE = 0;
65  
66  	/**
67  	 * Option to recursively delete given {@code File}
68  	 */
69  	public static final int RECURSIVE = 1;
70  
71  	/**
72  	 * Option to retry deletion if not successful
73  	 */
74  	public static final int RETRY = 2;
75  
76  	/**
77  	 * Option to skip deletion if file doesn't exist
78  	 */
79  	public static final int SKIP_MISSING = 4;
80  
81  	/**
82  	 * Option not to throw exceptions when a deletion finally doesn't succeed.
83  	 * @since 2.0
84  	 */
85  	public static final int IGNORE_ERRORS = 8;
86  
87  	/**
88  	 * Option to only delete empty directories. This option can be combined with
89  	 * {@link #RECURSIVE}
90  	 *
91  	 * @since 3.0
92  	 */
93  	public static final int EMPTY_DIRECTORIES_ONLY = 16;
94  
95  	/**
96  	 * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
97  	 *
98  	 * @param f
99  	 *            {@code File} to be converted to {@code Path}
100 	 * @return the path represented by the file
101 	 * @throws java.io.IOException
102 	 *             in case the path represented by the file is not valid (
103 	 *             {@link java.nio.file.InvalidPathException})
104 	 * @since 4.10
105 	 */
106 	public static Path toPath(File f) throws IOException {
107 		try {
108 			return f.toPath();
109 		} catch (InvalidPathException ex) {
110 			throw new IOException(ex);
111 		}
112 	}
113 
114 	/**
115 	 * Delete file or empty folder
116 	 *
117 	 * @param f
118 	 *            {@code File} to be deleted
119 	 * @throws java.io.IOException
120 	 *             if deletion of {@code f} fails. This may occur if {@code f}
121 	 *             didn't exist when the method was called. This can therefore
122 	 *             cause java.io.IOExceptions during race conditions when
123 	 *             multiple concurrent threads all try to delete the same file.
124 	 */
125 	public static void delete(File f) throws IOException {
126 		delete(f, NONE);
127 	}
128 
129 	/**
130 	 * Delete file or folder
131 	 *
132 	 * @param f
133 	 *            {@code File} to be deleted
134 	 * @param options
135 	 *            deletion options, {@code RECURSIVE} for recursive deletion of
136 	 *            a subtree, {@code RETRY} to retry when deletion failed.
137 	 *            Retrying may help if the underlying file system doesn't allow
138 	 *            deletion of files being read by another thread.
139 	 * @throws java.io.IOException
140 	 *             if deletion of {@code f} fails. This may occur if {@code f}
141 	 *             didn't exist when the method was called. This can therefore
142 	 *             cause java.io.IOExceptions during race conditions when
143 	 *             multiple concurrent threads all try to delete the same file.
144 	 *             This exception is not thrown when IGNORE_ERRORS is set.
145 	 */
146 	public static void delete(File f, int options) throws IOException {
147 		FS fs = FS.DETECTED;
148 		if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
149 			return;
150 
151 		if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
152 			final File[] items = f.listFiles();
153 			if (items != null) {
154 				List<File> files = new ArrayList<>();
155 				List<File> dirs = new ArrayList<>();
156 				for (File c : items)
157 					if (c.isFile())
158 						files.add(c);
159 					else
160 						dirs.add(c);
161 				// Try to delete files first, otherwise options
162 				// EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
163 				// directories before aborting, depending on order.
164 				for (File file : files)
165 					delete(file, options);
166 				for (File d : dirs)
167 					delete(d, options);
168 			}
169 		}
170 
171 		boolean delete = false;
172 		if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
173 			if (f.isDirectory()) {
174 				delete = true;
175 			} else if ((options & IGNORE_ERRORS) == 0) {
176 				throw new IOException(MessageFormat.format(
177 						JGitText.get().deleteFileFailed, f.getAbsolutePath()));
178 			}
179 		} else {
180 			delete = true;
181 		}
182 
183 		if (delete) {
184 			IOException t = null;
185 			Path p = f.toPath();
186 			boolean tryAgain;
187 			do {
188 				tryAgain = false;
189 				try {
190 					Files.delete(p);
191 					return;
192 				} catch (NoSuchFileException | FileNotFoundException e) {
193 					handleDeleteException(f, e, options,
194 							SKIP_MISSING | IGNORE_ERRORS);
195 					return;
196 				} catch (DirectoryNotEmptyException e) {
197 					handleDeleteException(f, e, options, IGNORE_ERRORS);
198 					return;
199 				} catch (IOException e) {
200 					if (!f.canWrite()) {
201 						tryAgain = f.setWritable(true);
202 					}
203 					if (!tryAgain) {
204 						t = e;
205 					}
206 				}
207 			} while (tryAgain);
208 
209 			if ((options & RETRY) != 0) {
210 				for (int i = 1; i < 10; i++) {
211 					try {
212 						Thread.sleep(100);
213 					} catch (InterruptedException ex) {
214 						// ignore
215 					}
216 					try {
217 						Files.deleteIfExists(p);
218 						return;
219 					} catch (IOException e) {
220 						t = e;
221 					}
222 				}
223 			}
224 			handleDeleteException(f, t, options, IGNORE_ERRORS);
225 		}
226 	}
227 
228 	private static void handleDeleteException(File f, IOException e,
229 			int allOptions, int checkOptions) throws IOException {
230 		if (e != null && (allOptions & checkOptions) == 0) {
231 			throw new IOException(MessageFormat.format(
232 					JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
233 		}
234 	}
235 
236 	/**
237 	 * Rename a file or folder. If the rename fails and if we are running on a
238 	 * filesystem where it makes sense to repeat a failing rename then repeat
239 	 * the rename operation up to 9 times with 100ms sleep time between two
240 	 * calls. Furthermore if the destination exists and is directory hierarchy
241 	 * with only directories in it, the whole directory hierarchy will be
242 	 * deleted. If the target represents a non-empty directory structure, empty
243 	 * subdirectories within that structure may or may not be deleted even if
244 	 * the method fails. Furthermore if the destination exists and is a file
245 	 * then the file will be deleted and then the rename is retried.
246 	 * <p>
247 	 * This operation is <em>not</em> atomic.
248 	 *
249 	 * @see FS#retryFailedLockFileCommit()
250 	 * @param src
251 	 *            the old {@code File}
252 	 * @param dst
253 	 *            the new {@code File}
254 	 * @throws java.io.IOException
255 	 *             if the rename has failed
256 	 * @since 3.0
257 	 */
258 	public static void rename(File src, File dst)
259 			throws IOException {
260 		rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
261 	}
262 
263 	/**
264 	 * Rename a file or folder using the passed
265 	 * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
266 	 * running on a filesystem where it makes sense to repeat a failing rename
267 	 * then repeat the rename operation up to 9 times with 100ms sleep time
268 	 * between two calls. Furthermore if the destination exists and is a
269 	 * directory hierarchy with only directories in it, the whole directory
270 	 * hierarchy will be deleted. If the target represents a non-empty directory
271 	 * structure, empty subdirectories within that structure may or may not be
272 	 * deleted even if the method fails. Furthermore if the destination exists
273 	 * and is a file then the file will be replaced if
274 	 * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
275 	 * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
276 	 * rename will be done atomically or fail with an
277 	 * {@link java.nio.file.AtomicMoveNotSupportedException}
278 	 *
279 	 * @param src
280 	 *            the old file
281 	 * @param dst
282 	 *            the new file
283 	 * @param options
284 	 *            options to pass to
285 	 *            {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
286 	 * @throws java.nio.file.AtomicMoveNotSupportedException
287 	 *             if file cannot be moved as an atomic file system operation
288 	 * @throws java.io.IOException
289 	 * @since 4.1
290 	 */
291 	public static void rename(final File src, final File dst,
292 			CopyOption... options)
293 					throws AtomicMoveNotSupportedException, IOException {
294 		int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
295 		while (--attempts >= 0) {
296 			try {
297 				Files.move(toPath(src), toPath(dst), options);
298 				return;
299 			} catch (AtomicMoveNotSupportedException e) {
300 				throw e;
301 			} catch (IOException e) {
302 				try {
303 					if (!dst.delete()) {
304 						delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
305 					}
306 					// On *nix there is no try, you do or do not
307 					Files.move(toPath(src), toPath(dst), options);
308 					return;
309 				} catch (IOException e2) {
310 					// ignore and continue retry
311 				}
312 			}
313 			try {
314 				Thread.sleep(100);
315 			} catch (InterruptedException e) {
316 				throw new IOException(
317 						MessageFormat.format(JGitText.get().renameFileFailed,
318 								src.getAbsolutePath(), dst.getAbsolutePath()),
319 						e);
320 			}
321 		}
322 		throw new IOException(
323 				MessageFormat.format(JGitText.get().renameFileFailed,
324 						src.getAbsolutePath(), dst.getAbsolutePath()));
325 	}
326 
327 	/**
328 	 * Creates the directory named by this abstract pathname.
329 	 *
330 	 * @param d
331 	 *            directory to be created
332 	 * @throws java.io.IOException
333 	 *             if creation of {@code d} fails. This may occur if {@code d}
334 	 *             did exist when the method was called. This can therefore
335 	 *             cause java.io.IOExceptions during race conditions when
336 	 *             multiple concurrent threads all try to create the same
337 	 *             directory.
338 	 */
339 	public static void mkdir(File d)
340 			throws IOException {
341 		mkdir(d, false);
342 	}
343 
344 	/**
345 	 * Creates the directory named by this abstract pathname.
346 	 *
347 	 * @param d
348 	 *            directory to be created
349 	 * @param skipExisting
350 	 *            if {@code true} skip creation of the given directory if it
351 	 *            already exists in the file system
352 	 * @throws java.io.IOException
353 	 *             if creation of {@code d} fails. This may occur if {@code d}
354 	 *             did exist when the method was called. This can therefore
355 	 *             cause java.io.IOExceptions during race conditions when
356 	 *             multiple concurrent threads all try to create the same
357 	 *             directory.
358 	 */
359 	public static void mkdir(File d, boolean skipExisting)
360 			throws IOException {
361 		if (!d.mkdir()) {
362 			if (skipExisting && d.isDirectory())
363 				return;
364 			throw new IOException(MessageFormat.format(
365 					JGitText.get().mkDirFailed, d.getAbsolutePath()));
366 		}
367 	}
368 
369 	/**
370 	 * Creates the directory named by this abstract pathname, including any
371 	 * necessary but nonexistent parent directories. Note that if this operation
372 	 * fails it may have succeeded in creating some of the necessary parent
373 	 * directories.
374 	 *
375 	 * @param d
376 	 *            directory to be created
377 	 * @throws java.io.IOException
378 	 *             if creation of {@code d} fails. This may occur if {@code d}
379 	 *             did exist when the method was called. This can therefore
380 	 *             cause java.io.IOExceptions during race conditions when
381 	 *             multiple concurrent threads all try to create the same
382 	 *             directory.
383 	 */
384 	public static void mkdirs(File d) throws IOException {
385 		mkdirs(d, false);
386 	}
387 
388 	/**
389 	 * Creates the directory named by this abstract pathname, including any
390 	 * necessary but nonexistent parent directories. Note that if this operation
391 	 * fails it may have succeeded in creating some of the necessary parent
392 	 * directories.
393 	 *
394 	 * @param d
395 	 *            directory to be created
396 	 * @param skipExisting
397 	 *            if {@code true} skip creation of the given directory if it
398 	 *            already exists in the file system
399 	 * @throws java.io.IOException
400 	 *             if creation of {@code d} fails. This may occur if {@code d}
401 	 *             did exist when the method was called. This can therefore
402 	 *             cause java.io.IOExceptions during race conditions when
403 	 *             multiple concurrent threads all try to create the same
404 	 *             directory.
405 	 */
406 	public static void mkdirs(File d, boolean skipExisting)
407 			throws IOException {
408 		if (!d.mkdirs()) {
409 			if (skipExisting && d.isDirectory())
410 				return;
411 			throw new IOException(MessageFormat.format(
412 					JGitText.get().mkDirsFailed, d.getAbsolutePath()));
413 		}
414 	}
415 
416 	/**
417 	 * Atomically creates a new, empty file named by this abstract pathname if
418 	 * and only if a file with this name does not yet exist. The check for the
419 	 * existence of the file and the creation of the file if it does not exist
420 	 * are a single operation that is atomic with respect to all other
421 	 * filesystem activities that might affect the file.
422 	 * <p>
423 	 * Note: this method should not be used for file-locking, as the resulting
424 	 * protocol cannot be made to work reliably. The
425 	 * {@link java.nio.channels.FileLock} facility should be used instead.
426 	 *
427 	 * @param f
428 	 *            the file to be created
429 	 * @throws java.io.IOException
430 	 *             if the named file already exists or if an I/O error occurred
431 	 */
432 	public static void createNewFile(File f) throws IOException {
433 		if (!f.createNewFile())
434 			throw new IOException(MessageFormat.format(
435 					JGitText.get().createNewFileFailed, f));
436 	}
437 
438 	/**
439 	 * Create a symbolic link
440 	 *
441 	 * @param path
442 	 *            the path of the symbolic link to create
443 	 * @param target
444 	 *            the target of the symbolic link
445 	 * @return the path to the symbolic link
446 	 * @throws java.io.IOException
447 	 * @since 4.2
448 	 */
449 	public static Path createSymLink(File path, String target)
450 			throws IOException {
451 		Path nioPath = toPath(path);
452 		if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
453 			BasicFileAttributes attrs = Files.readAttributes(nioPath,
454 					BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
455 			if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
456 				delete(path);
457 			} else {
458 				delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
459 			}
460 		}
461 		if (SystemReader.getInstance().isWindows()) {
462 			target = target.replace('/', '\\');
463 		}
464 		Path nioTarget = toPath(new File(target));
465 		return Files.createSymbolicLink(nioPath, nioTarget);
466 	}
467 
468 	/**
469 	 * Read target path of the symlink.
470 	 *
471 	 * @param path
472 	 *            a {@link java.io.File} object.
473 	 * @return target path of the symlink, or null if it is not a symbolic link
474 	 * @throws java.io.IOException
475 	 * @since 3.0
476 	 */
477 	public static String readSymLink(File path) throws IOException {
478 		Path nioPath = toPath(path);
479 		Path target = Files.readSymbolicLink(nioPath);
480 		String targetString = target.toString();
481 		if (SystemReader.getInstance().isWindows()) {
482 			targetString = targetString.replace('\\', '/');
483 		} else if (SystemReader.getInstance().isMacOS()) {
484 			targetString = Normalizer.normalize(targetString, Form.NFC);
485 		}
486 		return targetString;
487 	}
488 
489 	/**
490 	 * Create a temporary directory.
491 	 *
492 	 * @param prefix
493 	 *            prefix string
494 	 * @param suffix
495 	 *            suffix string
496 	 * @param dir
497 	 *            The parent dir, can be null to use system default temp dir.
498 	 * @return the temp dir created.
499 	 * @throws java.io.IOException
500 	 * @since 3.4
501 	 */
502 	public static File createTempDir(String prefix, String suffix, File dir)
503 			throws IOException {
504 		final int RETRIES = 1; // When something bad happens, retry once.
505 		for (int i = 0; i < RETRIES; i++) {
506 			File tmp = File.createTempFile(prefix, suffix, dir);
507 			if (!tmp.delete())
508 				continue;
509 			if (!tmp.mkdir())
510 				continue;
511 			return tmp;
512 		}
513 		throw new IOException(JGitText.get().cannotCreateTempDir);
514 	}
515 
516 	/**
517 	 * Expresses <code>other</code> as a relative file path from
518 	 * <code>base</code>. File-separator and case sensitivity are based on the
519 	 * current file system.
520 	 *
521 	 * See also
522 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
523 	 *
524 	 * @param base
525 	 *            Base path
526 	 * @param other
527 	 *            Destination path
528 	 * @return Relative path from <code>base</code> to <code>other</code>
529 	 * @since 4.8
530 	 */
531 	public static String relativizeNativePath(String base, String other) {
532 		return FS.DETECTED.relativize(base, other);
533 	}
534 
535 	/**
536 	 * Expresses <code>other</code> as a relative file path from
537 	 * <code>base</code>. File-separator and case sensitivity are based on Git's
538 	 * internal representation of files (which matches Unix).
539 	 *
540 	 * See also
541 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
542 	 *
543 	 * @param base
544 	 *            Base path
545 	 * @param other
546 	 *            Destination path
547 	 * @return Relative path from <code>base</code> to <code>other</code>
548 	 * @since 4.8
549 	 */
550 	public static String relativizeGitPath(String base, String other) {
551 		return relativizePath(base, other, "/", false); //$NON-NLS-1$
552 	}
553 
554 
555 	/**
556 	 * Expresses <code>other</code> as a relative file path from <code>base</code>
557 	 * <p>
558 	 * For example, if called with the two following paths :
559 	 *
560 	 * <pre>
561 	 * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
562 	 * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
563 	 * </pre>
564 	 *
565 	 * This will return "..\\another_project\\pom.xml".
566 	 *
567 	 * <p>
568 	 * <b>Note</b> that this will return the empty String if <code>base</code>
569 	 * and <code>other</code> are equal.
570 	 * </p>
571 	 *
572 	 * @param base
573 	 *            The path against which <code>other</code> should be
574 	 *            relativized. This will be assumed to denote the path to a
575 	 *            folder and not a file.
576 	 * @param other
577 	 *            The path that will be made relative to <code>base</code>.
578 	 * @param dirSeparator
579 	 *            A string that separates components of the path. In practice, this is "/" or "\\".
580 	 * @param caseSensitive
581 	 *            Whether to consider differently-cased directory names as distinct
582 	 * @return A relative path that, when resolved against <code>base</code>,
583 	 *         will yield the original <code>other</code>.
584 	 * @since 4.8
585 	 */
586 	public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
587 		if (base.equals(other))
588 			return ""; //$NON-NLS-1$
589 
590 		final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
591 		final String[] otherSegments = other.split(Pattern
592 				.quote(dirSeparator));
593 
594 		int commonPrefix = 0;
595 		while (commonPrefix < baseSegments.length
596 				&& commonPrefix < otherSegments.length) {
597 			if (caseSensitive
598 					&& baseSegments[commonPrefix]
599 					.equals(otherSegments[commonPrefix]))
600 				commonPrefix++;
601 			else if (!caseSensitive
602 					&& baseSegments[commonPrefix]
603 							.equalsIgnoreCase(otherSegments[commonPrefix]))
604 				commonPrefix++;
605 			else
606 				break;
607 		}
608 
609 		final StringBuilder builder = new StringBuilder();
610 		for (int i = commonPrefix; i < baseSegments.length; i++)
611 			builder.append("..").append(dirSeparator); //$NON-NLS-1$
612 		for (int i = commonPrefix; i < otherSegments.length; i++) {
613 			builder.append(otherSegments[i]);
614 			if (i < otherSegments.length - 1)
615 				builder.append(dirSeparator);
616 		}
617 		return builder.toString();
618 	}
619 
620 	/**
621 	 * Determine if an IOException is a Stale NFS File Handle
622 	 *
623 	 * @param ioe
624 	 *            an {@link java.io.IOException} object.
625 	 * @return a boolean true if the IOException is a Stale NFS FIle Handle
626 	 * @since 4.1
627 	 */
628 	public static boolean isStaleFileHandle(IOException ioe) {
629 		String msg = ioe.getMessage();
630 		return msg != null
631 				&& msg.toLowerCase(Locale.ROOT)
632 						.matches("stale .*file .*handle"); //$NON-NLS-1$
633 	}
634 
635 	/**
636 	 * Determine if a throwable or a cause in its causal chain is a Stale NFS
637 	 * File Handle
638 	 *
639 	 * @param throwable
640 	 *            a {@link java.lang.Throwable} object.
641 	 * @return a boolean true if the throwable or a cause in its causal chain is
642 	 *         a Stale NFS File Handle
643 	 * @since 4.7
644 	 */
645 	public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
646 		while (throwable != null) {
647 			if (throwable instanceof IOException
648 					&& isStaleFileHandle((IOException) throwable)) {
649 				return true;
650 			}
651 			throwable = throwable.getCause();
652 		}
653 		return false;
654 	}
655 
656 	/**
657 	 * @param file
658 	 * @return {@code true} if the passed file is a symbolic link
659 	 */
660 	static boolean isSymlink(File file) {
661 		return Files.isSymbolicLink(file.toPath());
662 	}
663 
664 	/**
665 	 * @param file
666 	 * @return lastModified attribute for given file, not following symbolic
667 	 *         links
668 	 * @throws IOException
669 	 * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
670 	 *             FileTime
671 	 */
672 	@Deprecated
673 	static long lastModified(File file) throws IOException {
674 		return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
675 				.toMillis();
676 	}
677 
678 	/**
679 	 * @param path
680 	 * @return lastModified attribute for given file, not following symbolic
681 	 *         links
682 	 */
683 	static Instant lastModifiedInstant(Path path) {
684 		try {
685 			return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
686 					.toInstant();
687 		} catch (NoSuchFileException e) {
688 			LOG.debug(
689 					"Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
690 					path);
691 			return Instant.EPOCH;
692 		} catch (IOException e) {
693 			LOG.error(MessageFormat
694 					.format(JGitText.get().readLastModifiedFailed, path), e);
695 			return Instant.ofEpochMilli(path.toFile().lastModified());
696 		}
697 	}
698 
699 	/**
700 	 * Return all the attributes of a file, without following symbolic links.
701 	 *
702 	 * @param file
703 	 * @return {@link BasicFileAttributes} of the file
704 	 * @throws IOException in case of any I/O errors accessing the file
705 	 *
706 	 * @since 4.5.6
707 	 */
708 	static BasicFileAttributes fileAttributes(File file) throws IOException {
709 		return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
710 	}
711 
712 	/**
713 	 * @param file
714 	 * @param time
715 	 * @throws IOException
716 	 */
717 	@Deprecated
718 	static void setLastModified(File file, long time) throws IOException {
719 		Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
720 	}
721 
722 	/**
723 	 * @param path
724 	 * @param time
725 	 * @throws IOException
726 	 */
727 	static void setLastModified(Path path, Instant time)
728 			throws IOException {
729 		Files.setLastModifiedTime(path, FileTime.from(time));
730 	}
731 
732 	/**
733 	 * @param file
734 	 * @return {@code true} if the given file exists, not following symbolic
735 	 *         links
736 	 */
737 	static boolean exists(File file) {
738 		return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
739 	}
740 
741 	/**
742 	 * @param file
743 	 * @return {@code true} if the given file is hidden
744 	 * @throws IOException
745 	 */
746 	static boolean isHidden(File file) throws IOException {
747 		return Files.isHidden(toPath(file));
748 	}
749 
750 	/**
751 	 * Set a file hidden (on Windows)
752 	 *
753 	 * @param file
754 	 *            a {@link java.io.File} object.
755 	 * @param hidden
756 	 *            a boolean.
757 	 * @throws java.io.IOException
758 	 * @since 4.1
759 	 */
760 	public static void setHidden(File file, boolean hidden) throws IOException {
761 		Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
762 				LinkOption.NOFOLLOW_LINKS);
763 	}
764 
765 	/**
766 	 * Get file length
767 	 *
768 	 * @param file
769 	 *            a {@link java.io.File}.
770 	 * @return length of the given file
771 	 * @throws java.io.IOException
772 	 * @since 4.1
773 	 */
774 	public static long getLength(File file) throws IOException {
775 		Path nioPath = toPath(file);
776 		if (Files.isSymbolicLink(nioPath))
777 			return Files.readSymbolicLink(nioPath).toString()
778 					.getBytes(UTF_8).length;
779 		return Files.size(nioPath);
780 	}
781 
782 	/**
783 	 * @param file
784 	 * @return {@code true} if the given file is a directory, not following
785 	 *         symbolic links
786 	 */
787 	static boolean isDirectory(File file) {
788 		return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
789 	}
790 
791 	/**
792 	 * @param file
793 	 * @return {@code true} if the given file is a file, not following symbolic
794 	 *         links
795 	 */
796 	static boolean isFile(File file) {
797 		return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
798 	}
799 
800 	/**
801 	 * Whether the given file can be executed.
802 	 *
803 	 * @param file
804 	 *            a {@link java.io.File} object.
805 	 * @return {@code true} if the given file can be executed.
806 	 * @since 4.1
807 	 */
808 	public static boolean canExecute(File file) {
809 		if (!isFile(file)) {
810 			return false;
811 		}
812 		return Files.isExecutable(file.toPath());
813 	}
814 
815 	/**
816 	 * @param fs
817 	 * @param file
818 	 * @return non null attributes object
819 	 */
820 	static Attributes getFileAttributesBasic(FS fs, File file) {
821 		try {
822 			Path nioPath = toPath(file);
823 			BasicFileAttributes readAttributes = nioPath
824 					.getFileSystem()
825 					.provider()
826 					.getFileAttributeView(nioPath,
827 							BasicFileAttributeView.class,
828 							LinkOption.NOFOLLOW_LINKS).readAttributes();
829 			Attributes attributes = new Attributes(fs, file,
830 					true,
831 					readAttributes.isDirectory(),
832 					fs.supportsExecute() ? file.canExecute() : false,
833 					readAttributes.isSymbolicLink(),
834 					readAttributes.isRegularFile(), //
835 					readAttributes.creationTime().toMillis(), //
836 					readAttributes.lastModifiedTime().toInstant(),
837 					readAttributes.isSymbolicLink() ? Constants
838 							.encode(readSymLink(file)).length
839 							: readAttributes.size());
840 			return attributes;
841 		} catch (IOException e) {
842 			return new Attributes(file, fs);
843 		}
844 	}
845 
846 	/**
847 	 * Get file system attributes for the given file.
848 	 *
849 	 * @param fs
850 	 *            a {@link org.eclipse.jgit.util.FS} object.
851 	 * @param file
852 	 *            a {@link java.io.File}.
853 	 * @return file system attributes for the given file.
854 	 * @since 4.1
855 	 */
856 	public static Attributes getFileAttributesPosix(FS fs, File file) {
857 		try {
858 			Path nioPath = toPath(file);
859 			PosixFileAttributes readAttributes = nioPath
860 					.getFileSystem()
861 					.provider()
862 					.getFileAttributeView(nioPath,
863 							PosixFileAttributeView.class,
864 							LinkOption.NOFOLLOW_LINKS).readAttributes();
865 			Attributes attributes = new Attributes(
866 					fs,
867 					file,
868 					true, //
869 					readAttributes.isDirectory(), //
870 					readAttributes.permissions().contains(
871 							PosixFilePermission.OWNER_EXECUTE),
872 					readAttributes.isSymbolicLink(),
873 					readAttributes.isRegularFile(), //
874 					readAttributes.creationTime().toMillis(), //
875 					readAttributes.lastModifiedTime().toInstant(),
876 					readAttributes.size());
877 			return attributes;
878 		} catch (IOException e) {
879 			return new Attributes(file, fs);
880 		}
881 	}
882 
883 	/**
884 	 * NFC normalize a file (on Mac), otherwise do nothing
885 	 *
886 	 * @param file
887 	 *            a {@link java.io.File}.
888 	 * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
889 	 *         file
890 	 * @since 4.1
891 	 */
892 	public static File normalize(File file) {
893 		if (SystemReader.getInstance().isMacOS()) {
894 			// TODO: Would it be faster to check with isNormalized first
895 			// assuming normalized paths are much more common
896 			String normalized = Normalizer.normalize(file.getPath(),
897 					Normalizer.Form.NFC);
898 			return new File(normalized);
899 		}
900 		return file;
901 	}
902 
903 	/**
904 	 * On Mac: get NFC normalized form of given name, otherwise the given name.
905 	 *
906 	 * @param name
907 	 *            a {@link java.lang.String} object.
908 	 * @return on Mac: NFC normalized form of given name
909 	 * @since 4.1
910 	 */
911 	public static String normalize(String name) {
912 		if (SystemReader.getInstance().isMacOS()) {
913 			if (name == null)
914 				return null;
915 			return Normalizer.normalize(name, Normalizer.Form.NFC);
916 		}
917 		return name;
918 	}
919 
920 	/**
921 	 * Best-effort variation of {@link java.io.File#getCanonicalFile()}
922 	 * returning the input file if the file cannot be canonicalized instead of
923 	 * throwing {@link java.io.IOException}.
924 	 *
925 	 * @param file
926 	 *            to be canonicalized; may be {@code null}
927 	 * @return canonicalized file, or the unchanged input file if
928 	 *         canonicalization failed or if {@code file == null}
929 	 * @throws java.lang.SecurityException
930 	 *             if {@link java.io.File#getCanonicalFile()} throws one
931 	 * @since 4.2
932 	 */
933 	public static File canonicalize(File file) {
934 		if (file == null) {
935 			return null;
936 		}
937 		try {
938 			return file.getCanonicalFile();
939 		} catch (IOException e) {
940 			return file;
941 		}
942 	}
943 
944 	/**
945 	 * Convert a path to String, replacing separators as necessary.
946 	 *
947 	 * @param file
948 	 *            a {@link java.io.File}.
949 	 * @return file's path as a String
950 	 * @since 4.10
951 	 */
952 	public static String pathToString(File file) {
953 		final String path = file.getPath();
954 		if (SystemReader.getInstance().isWindows()) {
955 			return path.replace('\\', '/');
956 		}
957 		return path;
958 	}
959 
960 	/**
961 	 * Touch the given file
962 	 *
963 	 * @param f
964 	 *            the file to touch
965 	 * @throws IOException
966 	 * @since 5.1.8
967 	 */
968 	public static void touch(Path f) throws IOException {
969 		try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
970 				StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
971 			// touch
972 		}
973 		Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
974 	}
975 
976 	/**
977 	 * Compute a delay in a {@code min..max} interval with random jitter.
978 	 *
979 	 * @param last
980 	 *            amount of delay waited before the last attempt. This is used
981 	 *            to seed the next delay interval. Should be 0 if there was no
982 	 *            prior delay.
983 	 * @param min
984 	 *            shortest amount of allowable delay between attempts.
985 	 * @param max
986 	 *            longest amount of allowable delay between attempts.
987 	 * @return new amount of delay to wait before the next attempt.
988 	 *
989 	 * @since 5.6
990 	 */
991 	public static long delay(long last, long min, long max) {
992 		long r = Math.max(0, last * 3 - min);
993 		if (r > 0) {
994 			int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
995 			r = RNG.nextInt(c);
996 		}
997 		return Math.max(Math.min(min + r, max), min);
998 	}
999 }