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