View Javadoc
1   /*
2    * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.util;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static java.time.Instant.EPOCH;
15  
16  import java.io.BufferedReader;
17  import java.io.ByteArrayInputStream;
18  import java.io.Closeable;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.OutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.Writer;
26  import java.nio.file.AccessDeniedException;
27  import java.nio.file.FileStore;
28  import java.nio.file.Files;
29  import java.nio.file.InvalidPathException;
30  import java.nio.file.Path;
31  import java.nio.file.attribute.BasicFileAttributes;
32  import java.nio.file.attribute.FileTime;
33  import java.security.AccessControlException;
34  import java.security.AccessController;
35  import java.security.PrivilegedAction;
36  import java.text.MessageFormat;
37  import java.time.Duration;
38  import java.time.Instant;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.HashMap;
42  import java.util.Map;
43  import java.util.Objects;
44  import java.util.Optional;
45  import java.util.UUID;
46  import java.util.concurrent.CancellationException;
47  import java.util.concurrent.CompletableFuture;
48  import java.util.concurrent.ConcurrentHashMap;
49  import java.util.concurrent.ExecutionException;
50  import java.util.concurrent.ExecutorService;
51  import java.util.concurrent.Executors;
52  import java.util.concurrent.LinkedBlockingQueue;
53  import java.util.concurrent.ThreadPoolExecutor;
54  import java.util.concurrent.TimeUnit;
55  import java.util.concurrent.TimeoutException;
56  import java.util.concurrent.atomic.AtomicBoolean;
57  import java.util.concurrent.atomic.AtomicInteger;
58  import java.util.concurrent.atomic.AtomicReference;
59  import java.util.concurrent.locks.Lock;
60  import java.util.concurrent.locks.ReentrantLock;
61  import java.util.regex.Matcher;
62  import java.util.regex.Pattern;
63  
64  import org.eclipse.jgit.annotations.NonNull;
65  import org.eclipse.jgit.annotations.Nullable;
66  import org.eclipse.jgit.api.errors.JGitInternalException;
67  import org.eclipse.jgit.errors.CommandFailedException;
68  import org.eclipse.jgit.errors.ConfigInvalidException;
69  import org.eclipse.jgit.errors.LockFailedException;
70  import org.eclipse.jgit.internal.JGitText;
71  import org.eclipse.jgit.internal.storage.file.FileSnapshot;
72  import org.eclipse.jgit.lib.Config;
73  import org.eclipse.jgit.lib.ConfigConstants;
74  import org.eclipse.jgit.lib.Constants;
75  import org.eclipse.jgit.lib.Repository;
76  import org.eclipse.jgit.lib.StoredConfig;
77  import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
78  import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
79  import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
80  import org.eclipse.jgit.util.ProcessResult.Status;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  /**
85   * Abstraction to support various file system operations not in Java.
86   */
87  public abstract class FS {
88  	private static final Logger LOG = LoggerFactory.getLogger(FS.class);
89  
90  	/**
91  	 * An empty array of entries, suitable as a return value for
92  	 * {@link #list(File, FileModeStrategy)}.
93  	 *
94  	 * @since 5.0
95  	 */
96  	protected static final Entry[] NO_ENTRIES = {};
97  
98  	private static final Pattern VERSION = Pattern
99  			.compile("\\s(\\d+)\\.(\\d+)\\.(\\d+)"); //$NON-NLS-1$
100 
101 	private volatile Boolean supportSymlinks;
102 
103 	/**
104 	 * This class creates FS instances. It will be overridden by a Java7 variant
105 	 * if such can be detected in {@link #detect(Boolean)}.
106 	 *
107 	 * @since 3.0
108 	 */
109 	public static class FSFactory {
110 		/**
111 		 * Constructor
112 		 */
113 		protected FSFactory() {
114 			// empty
115 		}
116 
117 		/**
118 		 * Detect the file system
119 		 *
120 		 * @param cygwinUsed
121 		 * @return FS instance
122 		 */
123 		public FS detect(Boolean cygwinUsed) {
124 			if (SystemReader.getInstance().isWindows()) {
125 				if (cygwinUsed == null) {
126 					cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
127 				}
128 				if (cygwinUsed.booleanValue()) {
129 					return new FS_Win32_Cygwin();
130 				}
131 				return new FS_Win32();
132 			}
133 			return new FS_POSIX();
134 		}
135 	}
136 
137 	/**
138 	 * Result of an executed process. The caller is responsible to close the
139 	 * contained {@link TemporaryBuffer}s
140 	 *
141 	 * @since 4.2
142 	 */
143 	public static class ExecutionResult {
144 		private TemporaryBuffer stdout;
145 
146 		private TemporaryBuffer stderr;
147 
148 		private int rc;
149 
150 		/**
151 		 * @param stdout
152 		 * @param stderr
153 		 * @param rc
154 		 */
155 		public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
156 				int rc) {
157 			this.stdout = stdout;
158 			this.stderr = stderr;
159 			this.rc = rc;
160 		}
161 
162 		/**
163 		 * @return buffered standard output stream
164 		 */
165 		public TemporaryBuffer getStdout() {
166 			return stdout;
167 		}
168 
169 		/**
170 		 * @return buffered standard error stream
171 		 */
172 		public TemporaryBuffer getStderr() {
173 			return stderr;
174 		}
175 
176 		/**
177 		 * @return the return code of the process
178 		 */
179 		public int getRc() {
180 			return rc;
181 		}
182 	}
183 
184 	/**
185 	 * Attributes of FileStores on this system
186 	 *
187 	 * @since 5.1.9
188 	 */
189 	public static final class FileStoreAttributes {
190 
191 		/**
192 		 * Marker to detect undefined values when reading from the config file.
193 		 */
194 		private static final Duration UNDEFINED_DURATION = Duration
195 				.ofNanos(Long.MAX_VALUE);
196 
197 		/**
198 		 * Fallback filesystem timestamp resolution. The worst case timestamp
199 		 * resolution on FAT filesystems is 2 seconds.
200 		 * <p>
201 		 * Must be at least 1 second.
202 		 * </p>
203 		 */
204 		public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
205 				.ofMillis(2000);
206 
207 		/**
208 		 * Fallback FileStore attributes used when we can't measure the
209 		 * filesystem timestamp resolution. The last modified time granularity
210 		 * of FAT filesystems is 2 seconds.
211 		 */
212 		public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
213 				FALLBACK_TIMESTAMP_RESOLUTION);
214 
215 		private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS
216 				.toNanos(1);
217 
218 		private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS
219 				.toNanos(1);
220 
221 		private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1);
222 
223 		/**
224 		 * Minimum file system timestamp resolution granularity to check, in
225 		 * nanoseconds. Should be a positive power of ten smaller than
226 		 * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e.,
227 		 * minimum value is 1 nanosecond.
228 		 * <p>
229 		 * Currently set to 1 microsecond, but could also be lower still.
230 		 * </p>
231 		 */
232 		private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND;
233 
234 		private static final String JAVA_VERSION_PREFIX = System
235 				.getProperty("java.vendor") + '|' //$NON-NLS-1$
236 				+ System.getProperty("java.version") + '|'; //$NON-NLS-1$
237 
238 		private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
239 				.ofMillis(10);
240 
241 		private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
242 
243 		private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
244 				100, 0.2f);
245 
246 		private static final AtomicBoolean background = new AtomicBoolean();
247 
248 		private static final Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
249 
250 		private static final AtomicInteger threadNumber = new AtomicInteger(1);
251 
252 		/**
253 		 * Don't use the default thread factory of the ForkJoinPool for the
254 		 * CompletableFuture; it runs without any privileges, which causes
255 		 * trouble if a SecurityManager is present.
256 		 * <p>
257 		 * Instead use normal daemon threads. They'll belong to the
258 		 * SecurityManager's thread group, or use the one of the calling thread,
259 		 * as appropriate.
260 		 * </p>
261 		 *
262 		 * @see java.util.concurrent.Executors#newCachedThreadPool()
263 		 */
264 		private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor(
265 				0, 5, 30L, TimeUnit.SECONDS,
266 				new LinkedBlockingQueue<Runnable>(),
267 				runnable -> {
268 					Thread t = new Thread(runnable,
269 							"JGit-FileStoreAttributeReader-" //$NON-NLS-1$
270 							+ threadNumber.getAndIncrement());
271 					// Make sure these threads don't prevent application/JVM
272 					// shutdown.
273 					t.setDaemon(true);
274 					return t;
275 				});
276 
277 		/**
278 		 * Use a separate executor with at most one thread to synchronize
279 		 * writing to the config. We write asynchronously since the config
280 		 * itself might be on a different file system, which might otherwise
281 		 * lead to locking problems.
282 		 * <p>
283 		 * Writing the config must not use a daemon thread, otherwise we may
284 		 * leave an inconsistent state on disk when the JVM shuts down. Use a
285 		 * small keep-alive time to avoid delays on shut-down.
286 		 * </p>
287 		 */
288 		private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor(
289 				0, 1, 1L, TimeUnit.MILLISECONDS,
290 				new LinkedBlockingQueue<Runnable>(),
291 				runnable -> {
292 					Thread t = new Thread(runnable,
293 							"JGit-FileStoreAttributeWriter-" //$NON-NLS-1$
294 							+ threadNumber.getAndIncrement());
295 					// Make sure these threads do finish
296 					t.setDaemon(false);
297 					return t;
298 				});
299 
300 		static {
301 			// Shut down the SAVE_RUNNER on System.exit()
302 			Runtime.getRuntime().addShutdownHook(new Thread(() -> {
303 				try {
304 					SAVE_RUNNER.shutdownNow();
305 					SAVE_RUNNER.awaitTermination(100, TimeUnit.MILLISECONDS);
306 				} catch (Exception e) {
307 					// Ignore; we're shutting down
308 				}
309 			}));
310 		}
311 
312 		/**
313 		 * Whether FileStore attributes should be determined asynchronously
314 		 *
315 		 * @param async
316 		 *            whether FileStore attributes should be determined
317 		 *            asynchronously. If false access to cached attributes may
318 		 *            block for some seconds for the first call per FileStore
319 		 * @since 5.6.2
320 		 */
321 		public static void setBackground(boolean async) {
322 			background.set(async);
323 		}
324 
325 		/**
326 		 * Configures size and purge factor of the path-based cache for file
327 		 * system attributes. Caching of file system attributes avoids recurring
328 		 * lookup of @{code FileStore} of files which may be expensive on some
329 		 * platforms.
330 		 *
331 		 * @param maxSize
332 		 *            maximum size of the cache, default is 100
333 		 * @param purgeFactor
334 		 *            when the size of the map reaches maxSize the oldest
335 		 *            entries will be purged to free up some space for new
336 		 *            entries, {@code purgeFactor} is the fraction of
337 		 *            {@code maxSize} to purge when this happens
338 		 * @since 5.1.9
339 		 */
340 		public static void configureAttributesPathCache(int maxSize,
341 				float purgeFactor) {
342 			FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
343 		}
344 
345 		/**
346 		 * Get the FileStoreAttributes for the given FileStore
347 		 *
348 		 * @param path
349 		 *            file residing in the FileStore to get attributes for
350 		 * @return FileStoreAttributes for the given path.
351 		 */
352 		public static FileStoreAttributes get(Path path) {
353 			try {
354 				path = path.toAbsolutePath();
355 				Path dir = Files.isDirectory(path) ? path : path.getParent();
356 				if (dir == null) {
357 					return FALLBACK_FILESTORE_ATTRIBUTES;
358 				}
359 				FileStoreAttributes cached = attrCacheByPath.get(dir);
360 				if (cached != null) {
361 					return cached;
362 				}
363 				FileStoreAttributes attrs = getFileStoreAttributes(dir);
364 				if (attrs == null) {
365 					// Don't cache, result might be late
366 					return FALLBACK_FILESTORE_ATTRIBUTES;
367 				}
368 				attrCacheByPath.put(dir, attrs);
369 				return attrs;
370 			} catch (SecurityException e) {
371 				return FALLBACK_FILESTORE_ATTRIBUTES;
372 			}
373 		}
374 
375 		private static FileStoreAttributes getFileStoreAttributes(Path dir) {
376 			FileStore s;
377 			try {
378 				if (Files.exists(dir)) {
379 					s = Files.getFileStore(dir);
380 					FileStoreAttributes c = attributeCache.get(s);
381 					if (c != null) {
382 						return c;
383 					}
384 					if (!Files.isWritable(dir)) {
385 						// cannot measure resolution in a read-only directory
386 						LOG.debug(
387 								"{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
388 								Thread.currentThread(), dir);
389 						return FALLBACK_FILESTORE_ATTRIBUTES;
390 					}
391 				} else {
392 					// cannot determine FileStore of an unborn directory
393 					LOG.debug(
394 							"{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
395 							Thread.currentThread(), dir);
396 					return FALLBACK_FILESTORE_ATTRIBUTES;
397 				}
398 
399 				CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
400 						.supplyAsync(() -> {
401 							Lock lock = locks.computeIfAbsent(s,
402 									l -> new ReentrantLock());
403 							if (!lock.tryLock()) {
404 								LOG.debug(
405 										"{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
406 										Thread.currentThread(), dir);
407 								return Optional.empty();
408 							}
409 							Optional<FileStoreAttributes> attributes = Optional
410 									.empty();
411 							try {
412 								// Some earlier future might have set the value
413 								// and removed itself since we checked for the
414 								// value above. Hence check cache again.
415 								FileStoreAttributes c = attributeCache.get(s);
416 								if (c != null) {
417 									return Optional.of(c);
418 								}
419 								attributes = readFromConfig(s);
420 								if (attributes.isPresent()) {
421 									attributeCache.put(s, attributes.get());
422 									return attributes;
423 								}
424 
425 								Optional<Duration> resolution = measureFsTimestampResolution(
426 										s, dir);
427 								if (resolution.isPresent()) {
428 									c = new FileStoreAttributes(
429 											resolution.get());
430 									attributeCache.put(s, c);
431 									// for high timestamp resolution measure
432 									// minimal racy interval
433 									if (c.fsTimestampResolution
434 											.toNanos() < 100_000_000L) {
435 										c.minimalRacyInterval = measureMinimalRacyInterval(
436 												dir);
437 									}
438 									if (LOG.isDebugEnabled()) {
439 										LOG.debug(c.toString());
440 									}
441 									FileStoreAttributes newAttrs = c;
442 									SAVE_RUNNER.execute(
443 											() -> saveToConfig(s, newAttrs));
444 								}
445 								attributes = Optional.of(c);
446 							} finally {
447 								lock.unlock();
448 								locks.remove(s);
449 							}
450 							return attributes;
451 						}, FUTURE_RUNNER);
452 				f = f.exceptionally(e -> {
453 					LOG.error(e.getLocalizedMessage(), e);
454 					return Optional.empty();
455 				});
456 				// even if measuring in background wait a little - if the result
457 				// arrives, it's better than returning the large fallback
458 				boolean runInBackground = background.get();
459 				Optional<FileStoreAttributes> d = runInBackground ? f.get(
460 						100, TimeUnit.MILLISECONDS) : f.get();
461 				if (d.isPresent()) {
462 					return d.get();
463 				} else if (runInBackground) {
464 					// return null until measurement is finished
465 					return null;
466 				}
467 				// fall through and return fallback
468 			} catch (IOException | ExecutionException | CancellationException e) {
469 				LOG.error(e.getMessage(), e);
470 			} catch (TimeoutException | SecurityException e) {
471 				// use fallback
472 			} catch (InterruptedException e) {
473 				LOG.error(e.getMessage(), e);
474 				Thread.currentThread().interrupt();
475 			}
476 			LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$
477 					Thread.currentThread(), dir);
478 			return FALLBACK_FILESTORE_ATTRIBUTES;
479 		}
480 
481 		@SuppressWarnings("boxing")
482 		private static Duration measureMinimalRacyInterval(Path dir) {
483 			LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
484 					Thread.currentThread(), dir);
485 			int n = 0;
486 			int failures = 0;
487 			long racyNanos = 0;
488 			ArrayList<Long> deltas = new ArrayList<>();
489 			Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
490 			Instant end = Instant.now().plusSeconds(3);
491 			try {
492 				probe.toFile().deleteOnExit();
493 				Files.createFile(probe);
494 				do {
495 					n++;
496 					write(probe, "a"); //$NON-NLS-1$
497 					FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
498 					read(probe);
499 					write(probe, "b"); //$NON-NLS-1$
500 					if (!snapshot.isModified(probe.toFile())) {
501 						deltas.add(Long.valueOf(snapshot.lastDelta()));
502 						racyNanos = snapshot.lastRacyThreshold();
503 						failures++;
504 					}
505 				} while (Instant.now().compareTo(end) < 0);
506 			} catch (IOException e) {
507 				LOG.error(e.getMessage(), e);
508 				return FALLBACK_MIN_RACY_INTERVAL;
509 			} finally {
510 				deleteProbe(probe);
511 			}
512 			if (failures > 0) {
513 				Stats stats = new Stats();
514 				for (Long d : deltas) {
515 					stats.add(d);
516 				}
517 				LOG.debug(
518 						"delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$
519 								+ "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$
520 								+ " delta max [ns], delta avg [ns]," //$NON-NLS-1$
521 								+ " delta stddev [ns]\n" //$NON-NLS-1$
522 								+ "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
523 						n, failures, racyNanos, stats.min(), stats.max(),
524 						stats.avg(), stats.stddev());
525 				return Duration
526 						.ofNanos(Double.valueOf(stats.max()).longValue());
527 			}
528 			// since no failures occurred using the measured filesystem
529 			// timestamp resolution there is no need for minimal racy interval
530 			LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$
531 					Thread.currentThread());
532 			return Duration.ZERO;
533 		}
534 
535 		private static void write(Path p, String body) throws IOException {
536 			Path parent = p.getParent();
537 			if (parent != null) {
538 				FileUtils.mkdirs(parent.toFile(), true);
539 			}
540 			try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
541 					UTF_8)) {
542 				w.write(body);
543 			}
544 		}
545 
546 		private static String read(Path p) throws IOException {
547 			byte[] body = IO.readFully(p.toFile());
548 			return new String(body, 0, body.length, UTF_8);
549 		}
550 
551 		private static Optional<Duration> measureFsTimestampResolution(
552 			FileStore s, Path dir) {
553 			if (LOG.isDebugEnabled()) {
554 				LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
555 						Thread.currentThread(), s, dir);
556 			}
557 			Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
558 			try {
559 				probe.toFile().deleteOnExit();
560 				Files.createFile(probe);
561 				Duration fsResolution = getFsResolution(s, dir, probe);
562 				Duration clockResolution = measureClockResolution();
563 				fsResolution = fsResolution.plus(clockResolution);
564 				if (LOG.isDebugEnabled()) {
565 					LOG.debug(
566 							"{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$
567 							Thread.currentThread(), s, dir, fsResolution);
568 				}
569 				return Optional.of(fsResolution);
570 			} catch (SecurityException e) {
571 				// Log it here; most likely deleteProbe() below will also run
572 				// into a SecurityException, and then this one will be lost
573 				// without trace.
574 				LOG.warn(e.getLocalizedMessage(), e);
575 			} catch (AccessDeniedException e) {
576 				LOG.warn(e.getLocalizedMessage(), e); // see bug 548648
577 			} catch (IOException e) {
578 				LOG.error(e.getLocalizedMessage(), e);
579 			} finally {
580 				deleteProbe(probe);
581 			}
582 			return Optional.empty();
583 		}
584 
585 		private static Duration getFsResolution(FileStore s, Path dir,
586 				Path probe) throws IOException {
587 			File probeFile = probe.toFile();
588 			FileTime t1 = Files.getLastModifiedTime(probe);
589 			Instant t1i = t1.toInstant();
590 			FileTime t2;
591 			Duration last = FALLBACK_TIMESTAMP_RESOLUTION;
592 			long minScale = MINIMUM_RESOLUTION_NANOS;
593 			long scale = ONE_SECOND;
594 			long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis());
595 			long low = 0;
596 			// Try up-front at microsecond and millisecond
597 			long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND };
598 			for (long interval : tries) {
599 				if (interval >= ONE_MILLISECOND) {
600 					probeFile.setLastModified(
601 							t1i.plusNanos(interval).toEpochMilli());
602 				} else {
603 					Files.setLastModifiedTime(probe,
604 							FileTime.from(t1i.plusNanos(interval)));
605 				}
606 				t2 = Files.getLastModifiedTime(probe);
607 				if (t2.compareTo(t1) > 0) {
608 					Duration diff = Duration.between(t1i, t2.toInstant());
609 					if (!diff.isZero() && !diff.isNegative()
610 							&& diff.compareTo(last) < 0) {
611 						scale = interval;
612 						high = 1;
613 						last = diff;
614 						break;
615 					}
616 				} else {
617 					// Makes no sense going below
618 					minScale = Math.max(minScale, interval);
619 				}
620 			}
621 			// Binary search loop
622 			while (high > low) {
623 				long mid = (high + low) / 2;
624 				if (mid == 0) {
625 					// Smaller than current scale. Adjust scale.
626 					long newScale = scale / 10;
627 					if (newScale < minScale) {
628 						break;
629 					}
630 					high *= scale / newScale;
631 					low *= scale / newScale;
632 					scale = newScale;
633 					mid = (high + low) / 2;
634 				}
635 				long delta = mid * scale;
636 				if (scale >= ONE_MILLISECOND) {
637 					probeFile.setLastModified(
638 							t1i.plusNanos(delta).toEpochMilli());
639 				} else {
640 					Files.setLastModifiedTime(probe,
641 							FileTime.from(t1i.plusNanos(delta)));
642 				}
643 				t2 = Files.getLastModifiedTime(probe);
644 				int cmp = t2.compareTo(t1);
645 				if (cmp > 0) {
646 					high = mid;
647 					Duration diff = Duration.between(t1i, t2.toInstant());
648 					if (diff.isZero() || diff.isNegative()) {
649 						LOG.warn(JGitText.get().logInconsistentFiletimeDiff,
650 								Thread.currentThread(), s, dir, t2, t1, diff,
651 								last);
652 						break;
653 					} else if (diff.compareTo(last) > 0) {
654 						LOG.warn(JGitText.get().logLargerFiletimeDiff,
655 								Thread.currentThread(), s, dir, diff, last);
656 						break;
657 					}
658 					last = diff;
659 				} else if (cmp < 0) {
660 					LOG.warn(JGitText.get().logSmallerFiletime,
661 							Thread.currentThread(), s, dir, t2, t1, last);
662 					break;
663 				} else {
664 					// No discernible difference
665 					low = mid + 1;
666 				}
667 			}
668 			return last;
669 		}
670 
671 		private static Duration measureClockResolution() {
672 			Duration clockResolution = Duration.ZERO;
673 			for (int i = 0; i < 10; i++) {
674 				Instant t1 = Instant.now();
675 				Instant t2 = t1;
676 				while (t2.compareTo(t1) <= 0) {
677 					t2 = Instant.now();
678 				}
679 				Duration r = Duration.between(t1, t2);
680 				if (r.compareTo(clockResolution) > 0) {
681 					clockResolution = r;
682 				}
683 			}
684 			return clockResolution;
685 		}
686 
687 		private static void deleteProbe(Path probe) {
688 			try {
689 				FileUtils.delete(probe.toFile(),
690 						FileUtils.SKIP_MISSING | FileUtils.RETRY);
691 			} catch (IOException e) {
692 				LOG.error(e.getMessage(), e);
693 			}
694 		}
695 
696 		private static Optional<FileStoreAttributes> readFromConfig(
697 				FileStore s) {
698 			StoredConfig userConfig;
699 			try {
700 				userConfig = SystemReader.getInstance().getUserConfig();
701 			} catch (IOException | ConfigInvalidException e) {
702 				LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
703 				return Optional.empty();
704 			}
705 			String key = getConfigKey(s);
706 			Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
707 					ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
708 					ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
709 					UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
710 			if (UNDEFINED_DURATION.equals(resolution)) {
711 				return Optional.empty();
712 			}
713 			Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
714 					ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
715 					ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
716 					UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
717 			FileStoreAttributes c = new FileStoreAttributes(resolution);
718 			if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
719 				c.minimalRacyInterval = minRacyThreshold;
720 			}
721 			return Optional.of(c);
722 		}
723 
724 		private static void saveToConfig(FileStore s,
725 				FileStoreAttributes c) {
726 			StoredConfig jgitConfig;
727 			try {
728 				jgitConfig = SystemReader.getInstance().getJGitConfig();
729 			} catch (IOException | ConfigInvalidException e) {
730 				LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
731 				return;
732 			}
733 			long resolution = c.getFsTimestampResolution().toNanos();
734 			TimeUnit resolutionUnit = getUnit(resolution);
735 			long resolutionValue = resolutionUnit.convert(resolution,
736 					TimeUnit.NANOSECONDS);
737 
738 			long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
739 			TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
740 			long minRacyThresholdValue = minRacyThresholdUnit
741 					.convert(minRacyThreshold, TimeUnit.NANOSECONDS);
742 
743 			final int max_retries = 5;
744 			int retries = 0;
745 			boolean succeeded = false;
746 			String key = getConfigKey(s);
747 			while (!succeeded && retries < max_retries) {
748 				try {
749 					jgitConfig.setString(
750 							ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
751 							ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
752 							String.format("%d %s", //$NON-NLS-1$
753 									Long.valueOf(resolutionValue),
754 									resolutionUnit.name().toLowerCase()));
755 					jgitConfig.setString(
756 							ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
757 							ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
758 							String.format("%d %s", //$NON-NLS-1$
759 									Long.valueOf(minRacyThresholdValue),
760 									minRacyThresholdUnit.name().toLowerCase()));
761 					jgitConfig.save();
762 					succeeded = true;
763 				} catch (LockFailedException e) {
764 					// race with another thread, wait a bit and try again
765 					try {
766 						retries++;
767 						if (retries < max_retries) {
768 							Thread.sleep(100);
769 							LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$
770 									jgitConfig, Integer.valueOf(retries),
771 									Integer.valueOf(max_retries));
772 						} else {
773 							LOG.warn(MessageFormat.format(
774 									JGitText.get().lockFailedRetry, jgitConfig,
775 									Integer.valueOf(retries)));
776 						}
777 					} catch (InterruptedException e1) {
778 						Thread.currentThread().interrupt();
779 						break;
780 					}
781 				} catch (IOException e) {
782 					LOG.error(MessageFormat.format(
783 							JGitText.get().cannotSaveConfig, jgitConfig), e);
784 					break;
785 				}
786 			}
787 		}
788 
789 		private static String getConfigKey(FileStore s) {
790 			String storeKey;
791 			if (SystemReader.getInstance().isWindows()) {
792 				Object attribute = null;
793 				try {
794 					attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
795 				} catch (IOException ignored) {
796 					// ignore
797 				}
798 				if (attribute instanceof Integer) {
799 					storeKey = attribute.toString();
800 				} else {
801 					storeKey = s.name();
802 				}
803 			} else {
804 				storeKey = s.name();
805 			}
806 			return JAVA_VERSION_PREFIX + storeKey;
807 		}
808 
809 		private static TimeUnit getUnit(long nanos) {
810 			TimeUnit unit;
811 			if (nanos < 200_000L) {
812 				unit = TimeUnit.NANOSECONDS;
813 			} else if (nanos < 200_000_000L) {
814 				unit = TimeUnit.MICROSECONDS;
815 			} else {
816 				unit = TimeUnit.MILLISECONDS;
817 			}
818 			return unit;
819 		}
820 
821 		private final @NonNull Duration fsTimestampResolution;
822 
823 		private Duration minimalRacyInterval;
824 
825 		/**
826 		 * @return the measured minimal interval after a file has been modified
827 		 *         in which we cannot rely on lastModified to detect
828 		 *         modifications
829 		 */
830 		public Duration getMinimalRacyInterval() {
831 			return minimalRacyInterval;
832 		}
833 
834 		/**
835 		 * @return the measured filesystem timestamp resolution
836 		 */
837 		@NonNull
838 		public Duration getFsTimestampResolution() {
839 			return fsTimestampResolution;
840 		}
841 
842 		/**
843 		 * Construct a FileStoreAttributeCache entry for the given filesystem
844 		 * timestamp resolution
845 		 *
846 		 * @param fsTimestampResolution
847 		 */
848 		public FileStoreAttributes(
849 				@NonNull Duration fsTimestampResolution) {
850 			this.fsTimestampResolution = fsTimestampResolution;
851 			this.minimalRacyInterval = Duration.ZERO;
852 		}
853 
854 		@SuppressWarnings({ "nls", "boxing" })
855 		@Override
856 		public String toString() {
857 			return String.format(
858 					"FileStoreAttributes[fsTimestampResolution=%,d µs, "
859 							+ "minimalRacyInterval=%,d µs]",
860 					fsTimestampResolution.toNanos() / 1000,
861 					minimalRacyInterval.toNanos() / 1000);
862 		}
863 
864 	}
865 
866 	/** The auto-detected implementation selected for this operating system and JRE. */
867 	public static final FS DETECTED = detect();
868 
869 	private static volatile FSFactory factory;
870 
871 	/**
872 	 * Auto-detect the appropriate file system abstraction.
873 	 *
874 	 * @return detected file system abstraction
875 	 */
876 	public static FS detect() {
877 		return detect(null);
878 	}
879 
880 	/**
881 	 * Whether FileStore attributes should be determined asynchronously
882 	 *
883 	 * @param asynch
884 	 *            whether FileStore attributes should be determined
885 	 *            asynchronously. If false access to cached attributes may block
886 	 *            for some seconds for the first call per FileStore
887 	 * @since 5.1.9
888 	 * @deprecated Use {@link FileStoreAttributes#setBackground} instead
889 	 */
890 	@Deprecated
891 	public static void setAsyncFileStoreAttributes(boolean asynch) {
892 		FileStoreAttributes.setBackground(asynch);
893 	}
894 
895 	/**
896 	 * Auto-detect the appropriate file system abstraction, taking into account
897 	 * the presence of a Cygwin installation on the system. Using jgit in
898 	 * combination with Cygwin requires a more elaborate (and possibly slower)
899 	 * resolution of file system paths.
900 	 *
901 	 * @param cygwinUsed
902 	 *            <ul>
903 	 *            <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
904 	 *            combination with jgit</li>
905 	 *            <li><code>Boolean.FALSE</code> to assume that Cygwin is
906 	 *            <b>not</b> used with jgit</li>
907 	 *            <li><code>null</code> to auto-detect whether a Cygwin
908 	 *            installation is present on the system and in this case assume
909 	 *            that Cygwin is used</li>
910 	 *            </ul>
911 	 *
912 	 *            Note: this parameter is only relevant on Windows.
913 	 * @return detected file system abstraction
914 	 */
915 	public static FS detect(Boolean cygwinUsed) {
916 		if (factory == null) {
917 			factory = new FS.FSFactory();
918 		}
919 		return factory.detect(cygwinUsed);
920 	}
921 
922 	/**
923 	 * Get cached FileStore attributes, if not yet available measure them using
924 	 * a probe file under the given directory.
925 	 *
926 	 * @param dir
927 	 *            the directory under which the probe file will be created to
928 	 *            measure the timer resolution.
929 	 * @return measured filesystem timestamp resolution
930 	 * @since 5.1.9
931 	 */
932 	public static FileStoreAttributes getFileStoreAttributes(
933 			@NonNull Path dir) {
934 		return FileStoreAttributes.get(dir);
935 	}
936 
937 	private volatile Holder<File> userHome;
938 
939 	private volatile Holder<File> gitSystemConfig;
940 
941 	/**
942 	 * Constructs a file system abstraction.
943 	 */
944 	protected FS() {
945 		// Do nothing by default.
946 	}
947 
948 	/**
949 	 * Initialize this FS using another's current settings.
950 	 *
951 	 * @param src
952 	 *            the source FS to copy from.
953 	 */
954 	protected FS(FS src) {
955 		userHome = src.userHome;
956 		gitSystemConfig = src.gitSystemConfig;
957 	}
958 
959 	/**
960 	 * Create a new instance of the same type of FS.
961 	 *
962 	 * @return a new instance of the same type of FS.
963 	 */
964 	public abstract FS newInstance();
965 
966 	/**
967 	 * Does this operating system and JRE support the execute flag on files?
968 	 *
969 	 * @return true if this implementation can provide reasonably accurate
970 	 *         executable bit information; false otherwise.
971 	 */
972 	public abstract boolean supportsExecute();
973 
974 	/**
975 	 * Does this file system support atomic file creation via
976 	 * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
977 	 * not guaranteed that when two file system clients run createNewFile() in
978 	 * parallel only one will succeed. In such cases both clients may think they
979 	 * created a new file.
980 	 *
981 	 * @return true if this implementation support atomic creation of new Files
982 	 *         by {@link java.io.File#createNewFile()}
983 	 * @since 4.5
984 	 */
985 	public boolean supportsAtomicCreateNewFile() {
986 		return true;
987 	}
988 
989 	/**
990 	 * Does this operating system and JRE supports symbolic links. The
991 	 * capability to handle symbolic links is detected at runtime.
992 	 *
993 	 * @return true if symbolic links may be used
994 	 * @since 3.0
995 	 */
996 	public boolean supportsSymlinks() {
997 		if (supportSymlinks == null) {
998 			detectSymlinkSupport();
999 		}
1000 		return Boolean.TRUE.equals(supportSymlinks);
1001 	}
1002 
1003 	private void detectSymlinkSupport() {
1004 		File tempFile = null;
1005 		try {
1006 			tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$
1007 			File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$
1008 			createSymLink(linkName, tempFile.getPath());
1009 			supportSymlinks = Boolean.TRUE;
1010 			linkName.delete();
1011 		} catch (IOException | UnsupportedOperationException | SecurityException
1012 				| InternalError e) {
1013 			supportSymlinks = Boolean.FALSE;
1014 		} finally {
1015 			if (tempFile != null) {
1016 				try {
1017 					FileUtils.delete(tempFile);
1018 				} catch (IOException e) {
1019 					LOG.error(JGitText.get().cannotDeleteFile, tempFile);
1020 				}
1021 			}
1022 		}
1023 	}
1024 
1025 	/**
1026 	 * Is this file system case sensitive
1027 	 *
1028 	 * @return true if this implementation is case sensitive
1029 	 */
1030 	public abstract boolean isCaseSensitive();
1031 
1032 	/**
1033 	 * Determine if the file is executable (or not).
1034 	 * <p>
1035 	 * Not all platforms and JREs support executable flags on files. If the
1036 	 * feature is unsupported this method will always return false.
1037 	 * <p>
1038 	 * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
1039 	 * this method returns false, rather than the state of the executable flags
1040 	 * on the target file.</em>
1041 	 *
1042 	 * @param f
1043 	 *            abstract path to test.
1044 	 * @return true if the file is believed to be executable by the user.
1045 	 */
1046 	public abstract boolean canExecute(File f);
1047 
1048 	/**
1049 	 * Set a file to be executable by the user.
1050 	 * <p>
1051 	 * Not all platforms and JREs support executable flags on files. If the
1052 	 * feature is unsupported this method will always return false and no
1053 	 * changes will be made to the file specified.
1054 	 *
1055 	 * @param f
1056 	 *            path to modify the executable status of.
1057 	 * @param canExec
1058 	 *            true to enable execution; false to disable it.
1059 	 * @return true if the change succeeded; false otherwise.
1060 	 */
1061 	public abstract boolean setExecute(File f, boolean canExec);
1062 
1063 	/**
1064 	 * Get the last modified time of a file system object. If the OS/JRE support
1065 	 * symbolic links, the modification time of the link is returned, rather
1066 	 * than that of the link target.
1067 	 *
1068 	 * @param f
1069 	 *            a {@link java.io.File} object.
1070 	 * @return last modified time of f
1071 	 * @throws java.io.IOException
1072 	 * @since 3.0
1073 	 * @deprecated use {@link #lastModifiedInstant(Path)} instead
1074 	 */
1075 	@Deprecated
1076 	public long lastModified(File f) throws IOException {
1077 		return FileUtils.lastModified(f);
1078 	}
1079 
1080 	/**
1081 	 * Get the last modified time of a file system object. If the OS/JRE support
1082 	 * symbolic links, the modification time of the link is returned, rather
1083 	 * than that of the link target.
1084 	 *
1085 	 * @param p
1086 	 *            a {@link Path} object.
1087 	 * @return last modified time of p
1088 	 * @since 5.1.9
1089 	 */
1090 	public Instant lastModifiedInstant(Path p) {
1091 		return FileUtils.lastModifiedInstant(p);
1092 	}
1093 
1094 	/**
1095 	 * Get the last modified time of a file system object. If the OS/JRE support
1096 	 * symbolic links, the modification time of the link is returned, rather
1097 	 * than that of the link target.
1098 	 *
1099 	 * @param f
1100 	 *            a {@link File} object.
1101 	 * @return last modified time of p
1102 	 * @since 5.1.9
1103 	 */
1104 	public Instant lastModifiedInstant(File f) {
1105 		return FileUtils.lastModifiedInstant(f.toPath());
1106 	}
1107 
1108 	/**
1109 	 * Set the last modified time of a file system object.
1110 	 * <p>
1111 	 * For symlinks it sets the modified time of the link target.
1112 	 *
1113 	 * @param f
1114 	 *            a {@link java.io.File} object.
1115 	 * @param time
1116 	 *            last modified time
1117 	 * @throws java.io.IOException
1118 	 * @since 3.0
1119 	 * @deprecated use {@link #setLastModified(Path, Instant)} instead
1120 	 */
1121 	@Deprecated
1122 	public void setLastModified(File f, long time) throws IOException {
1123 		FileUtils.setLastModified(f, time);
1124 	}
1125 
1126 	/**
1127 	 * Set the last modified time of a file system object.
1128 	 * <p>
1129 	 * For symlinks it sets the modified time of the link target.
1130 	 *
1131 	 * @param p
1132 	 *            a {@link Path} object.
1133 	 * @param time
1134 	 *            last modified time
1135 	 * @throws java.io.IOException
1136 	 * @since 5.1.9
1137 	 */
1138 	public void setLastModified(Path p, Instant time) throws IOException {
1139 		FileUtils.setLastModified(p, time);
1140 	}
1141 
1142 	/**
1143 	 * Get the length of a file or link, If the OS/JRE supports symbolic links
1144 	 * it's the length of the link, else the length of the target.
1145 	 *
1146 	 * @param path
1147 	 *            a {@link java.io.File} object.
1148 	 * @return length of a file
1149 	 * @throws java.io.IOException
1150 	 * @since 3.0
1151 	 */
1152 	public long length(File path) throws IOException {
1153 		return FileUtils.getLength(path);
1154 	}
1155 
1156 	/**
1157 	 * Delete a file. Throws an exception if delete fails.
1158 	 *
1159 	 * @param f
1160 	 *            a {@link java.io.File} object.
1161 	 * @throws java.io.IOException
1162 	 *             this may be a Java7 subclass with detailed information
1163 	 * @since 3.3
1164 	 */
1165 	public void delete(File f) throws IOException {
1166 		FileUtils.delete(f);
1167 	}
1168 
1169 	/**
1170 	 * Resolve this file to its actual path name that the JRE can use.
1171 	 * <p>
1172 	 * This method can be relatively expensive. Computing a translation may
1173 	 * require forking an external process per path name translated. Callers
1174 	 * should try to minimize the number of translations necessary by caching
1175 	 * the results.
1176 	 * <p>
1177 	 * Not all platforms and JREs require path name translation. Currently only
1178 	 * Cygwin on Win32 require translation for Cygwin based paths.
1179 	 *
1180 	 * @param dir
1181 	 *            directory relative to which the path name is.
1182 	 * @param name
1183 	 *            path name to translate.
1184 	 * @return the translated path. <code>new File(dir,name)</code> if this
1185 	 *         platform does not require path name translation.
1186 	 */
1187 	public File resolve(File dir, String name) {
1188 		File abspn = new File(name);
1189 		if (abspn.isAbsolute())
1190 			return abspn;
1191 		return new File(dir, name);
1192 	}
1193 
1194 	/**
1195 	 * Determine the user's home directory (location where preferences are).
1196 	 * <p>
1197 	 * This method can be expensive on the first invocation if path name
1198 	 * translation is required. Subsequent invocations return a cached result.
1199 	 * <p>
1200 	 * Not all platforms and JREs require path name translation. Currently only
1201 	 * Cygwin on Win32 requires translation of the Cygwin HOME directory.
1202 	 *
1203 	 * @return the user's home directory; null if the user does not have one.
1204 	 */
1205 	public File userHome() {
1206 		Holder<File> p = userHome;
1207 		if (p == null) {
1208 			p = new Holder<>(safeUserHomeImpl());
1209 			userHome = p;
1210 		}
1211 		return p.value;
1212 	}
1213 
1214 	private File safeUserHomeImpl() {
1215 		File home;
1216 		try {
1217 			home = userHomeImpl();
1218 			if (home != null) {
1219 				home.toPath();
1220 				return home;
1221 			}
1222 		} catch (RuntimeException e) {
1223 			LOG.error(JGitText.get().exceptionWhileFindingUserHome, e);
1224 		}
1225 		home = defaultUserHomeImpl();
1226 		if (home != null) {
1227 			try {
1228 				home.toPath();
1229 				return home;
1230 			} catch (InvalidPathException e) {
1231 				LOG.error(MessageFormat
1232 						.format(JGitText.get().invalidHomeDirectory, home), e);
1233 			}
1234 		}
1235 		return null;
1236 	}
1237 
1238 	/**
1239 	 * Set the user's home directory location.
1240 	 *
1241 	 * @param path
1242 	 *            the location of the user's preferences; null if there is no
1243 	 *            home directory for the current user.
1244 	 * @return {@code this}.
1245 	 */
1246 	public FS setUserHome(File path) {
1247 		userHome = new Holder<>(path);
1248 		return this;
1249 	}
1250 
1251 	/**
1252 	 * Does this file system have problems with atomic renames?
1253 	 *
1254 	 * @return true if the caller should retry a failed rename of a lock file.
1255 	 */
1256 	public abstract boolean retryFailedLockFileCommit();
1257 
1258 	/**
1259 	 * Return all the attributes of a file, without following symbolic links.
1260 	 *
1261 	 * @param file
1262 	 * @return {@link BasicFileAttributes} of the file
1263 	 * @throws IOException in case of any I/O errors accessing the file
1264 	 *
1265 	 * @since 4.5.6
1266 	 */
1267 	public BasicFileAttributes fileAttributes(File file) throws IOException {
1268 		return FileUtils.fileAttributes(file);
1269 	}
1270 
1271 	/**
1272 	 * Determine the user's home directory (location where preferences are).
1273 	 *
1274 	 * @return the user's home directory; null if the user does not have one.
1275 	 */
1276 	protected File userHomeImpl() {
1277 		return defaultUserHomeImpl();
1278 	}
1279 
1280 	private File defaultUserHomeImpl() {
1281 		String home = AccessController.doPrivileged(
1282 				(PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
1283 		);
1284 		if (home == null || home.length() == 0)
1285 			return null;
1286 		return new File(home).getAbsoluteFile();
1287 	}
1288 
1289 	/**
1290 	 * Searches the given path to see if it contains one of the given files.
1291 	 * Returns the first it finds which is executable. Returns null if not found
1292 	 * or if path is null.
1293 	 *
1294 	 * @param path
1295 	 *            List of paths to search separated by File.pathSeparator
1296 	 * @param lookFor
1297 	 *            Files to search for in the given path
1298 	 * @return the first match found, or null
1299 	 * @since 3.0
1300 	 */
1301 	protected static File searchPath(String path, String... lookFor) {
1302 		if (path == null) {
1303 			return null;
1304 		}
1305 
1306 		for (String p : path.split(File.pathSeparator)) {
1307 			for (String command : lookFor) {
1308 				File file = new File(p, command);
1309 				try {
1310 					if (file.isFile() && file.canExecute()) {
1311 						return file.getAbsoluteFile();
1312 					}
1313 				} catch (SecurityException e) {
1314 					LOG.warn(MessageFormat.format(
1315 							JGitText.get().skipNotAccessiblePath,
1316 							file.getPath()));
1317 				}
1318 			}
1319 		}
1320 		return null;
1321 	}
1322 
1323 	/**
1324 	 * Execute a command and return a single line of output as a String
1325 	 *
1326 	 * @param dir
1327 	 *            Working directory for the command
1328 	 * @param command
1329 	 *            as component array
1330 	 * @param encoding
1331 	 *            to be used to parse the command's output
1332 	 * @return the one-line output of the command or {@code null} if there is
1333 	 *         none
1334 	 * @throws org.eclipse.jgit.errors.CommandFailedException
1335 	 *             thrown when the command failed (return code was non-zero)
1336 	 */
1337 	@Nullable
1338 	protected static String readPipe(File dir, String[] command,
1339 			String encoding) throws CommandFailedException {
1340 		return readPipe(dir, command, encoding, null);
1341 	}
1342 
1343 	/**
1344 	 * Execute a command and return a single line of output as a String
1345 	 *
1346 	 * @param dir
1347 	 *            Working directory for the command
1348 	 * @param command
1349 	 *            as component array
1350 	 * @param encoding
1351 	 *            to be used to parse the command's output
1352 	 * @param env
1353 	 *            Map of environment variables to be merged with those of the
1354 	 *            current process
1355 	 * @return the one-line output of the command or {@code null} if there is
1356 	 *         none
1357 	 * @throws org.eclipse.jgit.errors.CommandFailedException
1358 	 *             thrown when the command failed (return code was non-zero)
1359 	 * @since 4.0
1360 	 */
1361 	@Nullable
1362 	protected static String readPipe(File dir, String[] command,
1363 			String encoding, Map<String, String> env)
1364 			throws CommandFailedException {
1365 		boolean debug = LOG.isDebugEnabled();
1366 		try {
1367 			if (debug) {
1368 				LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
1369 						+ dir);
1370 			}
1371 			ProcessBuilder pb = new ProcessBuilder(command);
1372 			pb.directory(dir);
1373 			if (env != null) {
1374 				pb.environment().putAll(env);
1375 			}
1376 			Process p;
1377 			try {
1378 				p = pb.start();
1379 			} catch (IOException e) {
1380 				// Process failed to start
1381 				throw new CommandFailedException(-1, e.getMessage(), e);
1382 			}
1383 			p.getOutputStream().close();
1384 			GobblerThread gobbler = new GobblerThread(p, command, dir);
1385 			gobbler.start();
1386 			String r = null;
1387 			try (BufferedReader lineRead = new BufferedReader(
1388 					new InputStreamReader(p.getInputStream(), encoding))) {
1389 				r = lineRead.readLine();
1390 				if (debug) {
1391 					LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
1392 					LOG.debug("remaining output:\n"); //$NON-NLS-1$
1393 					String l;
1394 					while ((l = lineRead.readLine()) != null) {
1395 						LOG.debug(l);
1396 					}
1397 				}
1398 			}
1399 
1400 			for (;;) {
1401 				try {
1402 					int rc = p.waitFor();
1403 					gobbler.join();
1404 					if (rc == 0 && !gobbler.fail.get()) {
1405 						return r;
1406 					}
1407 					if (debug) {
1408 						LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
1409 					}
1410 					throw new CommandFailedException(rc,
1411 							gobbler.errorMessage.get(),
1412 							gobbler.exception.get());
1413 				} catch (InterruptedException ie) {
1414 					// Stop bothering me, I have a zombie to reap.
1415 				}
1416 			}
1417 		} catch (IOException e) {
1418 			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
1419 		} catch (AccessControlException e) {
1420 			LOG.warn(MessageFormat.format(
1421 					JGitText.get().readPipeIsNotAllowedRequiredPermission,
1422 					command, dir, e.getPermission()));
1423 		} catch (SecurityException e) {
1424 			LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
1425 					command, dir));
1426 		}
1427 		if (debug) {
1428 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
1429 		}
1430 		return null;
1431 	}
1432 
1433 	private static class GobblerThread extends Thread {
1434 
1435 		/* The process has 5 seconds to exit after closing stderr */
1436 		private static final int PROCESS_EXIT_TIMEOUT = 5;
1437 
1438 		private final Process p;
1439 		private final String desc;
1440 		private final String dir;
1441 		final AtomicBoolean fail = new AtomicBoolean();
1442 		final AtomicReference<String> errorMessage = new AtomicReference<>();
1443 		final AtomicReference<Throwable> exception = new AtomicReference<>();
1444 
1445 		GobblerThread(Process p, String[] command, File dir) {
1446 			this.p = p;
1447 			this.desc = Arrays.toString(command);
1448 			this.dir = Objects.toString(dir);
1449 		}
1450 
1451 		@Override
1452 		public void run() {
1453 			StringBuilder err = new StringBuilder();
1454 			try (InputStream is = p.getErrorStream()) {
1455 				int ch;
1456 				while ((ch = is.read()) != -1) {
1457 					err.append((char) ch);
1458 				}
1459 			} catch (IOException e) {
1460 				if (waitForProcessCompletion(e) && p.exitValue() != 0) {
1461 					setError(e, e.getMessage(), p.exitValue());
1462 					fail.set(true);
1463 				} else {
1464 					// ignore. command terminated faster and stream was just closed
1465 					// or the process didn't terminate within timeout
1466 				}
1467 			} finally {
1468 				if (waitForProcessCompletion(null) && err.length() > 0) {
1469 					setError(null, err.toString(), p.exitValue());
1470 					if (p.exitValue() != 0) {
1471 						fail.set(true);
1472 					}
1473 				}
1474 			}
1475 		}
1476 
1477 		@SuppressWarnings("boxing")
1478 		private boolean waitForProcessCompletion(IOException originalError) {
1479 			try {
1480 				if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
1481 					setError(originalError, MessageFormat.format(
1482 							JGitText.get().commandClosedStderrButDidntExit,
1483 							desc, PROCESS_EXIT_TIMEOUT), -1);
1484 					fail.set(true);
1485 					return false;
1486 				}
1487 			} catch (InterruptedException e) {
1488 				setError(originalError, MessageFormat.format(
1489 						JGitText.get().threadInterruptedWhileRunning, desc), -1);
1490 				fail.set(true);
1491 				return false;
1492 			}
1493 			return true;
1494 		}
1495 
1496 		private void setError(IOException e, String message, int exitCode) {
1497 			exception.set(e);
1498 			errorMessage.set(MessageFormat.format(
1499 					JGitText.get().exceptionCaughtDuringExecutionOfCommand,
1500 					desc, dir, Integer.valueOf(exitCode), message));
1501 		}
1502 	}
1503 
1504 	/**
1505 	 * Discover the path to the Git executable.
1506 	 *
1507 	 * @return the path to the Git executable or {@code null} if it cannot be
1508 	 *         determined.
1509 	 * @since 4.0
1510 	 */
1511 	protected abstract File discoverGitExe();
1512 
1513 	/**
1514 	 * Discover the path to the system-wide Git configuration file
1515 	 *
1516 	 * @return the path to the system-wide Git configuration file or
1517 	 *         {@code null} if it cannot be determined.
1518 	 * @since 4.0
1519 	 */
1520 	protected File discoverGitSystemConfig() {
1521 		File gitExe = discoverGitExe();
1522 		if (gitExe == null) {
1523 			return null;
1524 		}
1525 
1526 		// Bug 480782: Check if the discovered git executable is JGit CLI
1527 		String v;
1528 		try {
1529 			v = readPipe(gitExe.getParentFile(),
1530 					new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$
1531 					SystemReader.getInstance().getDefaultCharset().name());
1532 		} catch (CommandFailedException e) {
1533 			LOG.warn(e.getMessage());
1534 			return null;
1535 		}
1536 		if (StringUtils.isEmptyOrNull(v)
1537 				|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
1538 			return null;
1539 		}
1540 
1541 		if (parseVersion(v) < makeVersion(2, 8, 0)) {
1542 			// --show-origin was introduced in git 2.8.0. For older git: trick
1543 			// it into printing the path to the config file by using "echo" as
1544 			// the editor.
1545 			Map<String, String> env = new HashMap<>();
1546 			env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
1547 
1548 			String w;
1549 			try {
1550 				// This command prints the path even if it doesn't exist
1551 				w = readPipe(gitExe.getParentFile(),
1552 						new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
1553 								"--edit" }, //$NON-NLS-1$
1554 						SystemReader.getInstance().getDefaultCharset().name(),
1555 						env);
1556 			} catch (CommandFailedException e) {
1557 				LOG.warn(e.getMessage());
1558 				return null;
1559 			}
1560 			if (StringUtils.isEmptyOrNull(w)) {
1561 				return null;
1562 			}
1563 
1564 			return new File(w);
1565 		}
1566 		String w;
1567 		try {
1568 			w = readPipe(gitExe.getParentFile(),
1569 					new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
1570 							"--show-origin", "--list", "-z" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1571 					SystemReader.getInstance().getDefaultCharset().name());
1572 		} catch (CommandFailedException e) {
1573 			// This command fails if the system config doesn't exist
1574 			if (LOG.isDebugEnabled()) {
1575 				LOG.debug(e.getMessage());
1576 			}
1577 			return null;
1578 		}
1579 		if (w == null) {
1580 			return null;
1581 		}
1582 		// We get NUL-terminated items; the first one will be a file name,
1583 		// prefixed by "file:". (Using -z is crucial, otherwise git quotes file
1584 		// names with special characters.)
1585 		int nul = w.indexOf(0);
1586 		if (nul <= 0) {
1587 			return null;
1588 		}
1589 		w = w.substring(0, nul);
1590 		int colon = w.indexOf(':');
1591 		if (colon < 0) {
1592 			return null;
1593 		}
1594 		w = w.substring(colon + 1);
1595 		return w.isEmpty() ? null : new File(w);
1596 	}
1597 
1598 	private long parseVersion(String version) {
1599 		Matcher m = VERSION.matcher(version);
1600 		if (m.find()) {
1601 			try {
1602 				return makeVersion(
1603 						Integer.parseInt(m.group(1)),
1604 						Integer.parseInt(m.group(2)),
1605 						Integer.parseInt(m.group(3)));
1606 			} catch (NumberFormatException e) {
1607 				// Ignore
1608 			}
1609 		}
1610 		return -1;
1611 	}
1612 
1613 	private long makeVersion(int major, int minor, int patch) {
1614 		return ((major * 10_000L) + minor) * 10_000L + patch;
1615 	}
1616 
1617 	/**
1618 	 * Get the currently used path to the system-wide Git configuration file.
1619 	 *
1620 	 * @return the currently used path to the system-wide Git configuration file
1621 	 *         or {@code null} if none has been set.
1622 	 * @since 4.0
1623 	 */
1624 	public File getGitSystemConfig() {
1625 		if (gitSystemConfig == null) {
1626 			gitSystemConfig = new Holder<>(discoverGitSystemConfig());
1627 		}
1628 		return gitSystemConfig.value;
1629 	}
1630 
1631 	/**
1632 	 * Set the path to the system-wide Git configuration file to use.
1633 	 *
1634 	 * @param configFile
1635 	 *            the path to the config file.
1636 	 * @return {@code this}
1637 	 * @since 4.0
1638 	 */
1639 	public FS setGitSystemConfig(File configFile) {
1640 		gitSystemConfig = new Holder<>(configFile);
1641 		return this;
1642 	}
1643 
1644 	/**
1645 	 * Get the parent directory of this file's parent directory
1646 	 *
1647 	 * @param grandchild
1648 	 *            a {@link java.io.File} object.
1649 	 * @return the parent directory of this file's parent directory or
1650 	 *         {@code null} in case there's no grandparent directory
1651 	 * @since 4.0
1652 	 */
1653 	protected static File resolveGrandparentFile(File grandchild) {
1654 		if (grandchild != null) {
1655 			File parent = grandchild.getParentFile();
1656 			if (parent != null)
1657 				return parent.getParentFile();
1658 		}
1659 		return null;
1660 	}
1661 
1662 	/**
1663 	 * Check if a file is a symbolic link and read it
1664 	 *
1665 	 * @param path
1666 	 *            a {@link java.io.File} object.
1667 	 * @return target of link or null
1668 	 * @throws java.io.IOException
1669 	 * @since 3.0
1670 	 */
1671 	public String readSymLink(File path) throws IOException {
1672 		return FileUtils.readSymLink(path);
1673 	}
1674 
1675 	/**
1676 	 * Whether the path is a symbolic link (and we support these).
1677 	 *
1678 	 * @param path
1679 	 *            a {@link java.io.File} object.
1680 	 * @return true if the path is a symbolic link (and we support these)
1681 	 * @throws java.io.IOException
1682 	 * @since 3.0
1683 	 */
1684 	public boolean isSymLink(File path) throws IOException {
1685 		return FileUtils.isSymlink(path);
1686 	}
1687 
1688 	/**
1689 	 * Tests if the path exists, in case of a symbolic link, true even if the
1690 	 * target does not exist
1691 	 *
1692 	 * @param path
1693 	 *            a {@link java.io.File} object.
1694 	 * @return true if path exists
1695 	 * @since 3.0
1696 	 */
1697 	public boolean exists(File path) {
1698 		return FileUtils.exists(path);
1699 	}
1700 
1701 	/**
1702 	 * Check if path is a directory. If the OS/JRE supports symbolic links and
1703 	 * path is a symbolic link to a directory, this method returns false.
1704 	 *
1705 	 * @param path
1706 	 *            a {@link java.io.File} object.
1707 	 * @return true if file is a directory,
1708 	 * @since 3.0
1709 	 */
1710 	public boolean isDirectory(File path) {
1711 		return FileUtils.isDirectory(path);
1712 	}
1713 
1714 	/**
1715 	 * Examine if path represents a regular file. If the OS/JRE supports
1716 	 * symbolic links the test returns false if path represents a symbolic link.
1717 	 *
1718 	 * @param path
1719 	 *            a {@link java.io.File} object.
1720 	 * @return true if path represents a regular file
1721 	 * @since 3.0
1722 	 */
1723 	public boolean isFile(File path) {
1724 		return FileUtils.isFile(path);
1725 	}
1726 
1727 	/**
1728 	 * Whether path is hidden, either starts with . on unix or has the hidden
1729 	 * attribute in windows
1730 	 *
1731 	 * @param path
1732 	 *            a {@link java.io.File} object.
1733 	 * @return true if path is hidden, either starts with . on unix or has the
1734 	 *         hidden attribute in windows
1735 	 * @throws java.io.IOException
1736 	 * @since 3.0
1737 	 */
1738 	public boolean isHidden(File path) throws IOException {
1739 		return FileUtils.isHidden(path);
1740 	}
1741 
1742 	/**
1743 	 * Set the hidden attribute for file whose name starts with a period.
1744 	 *
1745 	 * @param path
1746 	 *            a {@link java.io.File} object.
1747 	 * @param hidden
1748 	 *            whether to set the file hidden
1749 	 * @throws java.io.IOException
1750 	 * @since 3.0
1751 	 */
1752 	public void setHidden(File path, boolean hidden) throws IOException {
1753 		FileUtils.setHidden(path, hidden);
1754 	}
1755 
1756 	/**
1757 	 * Create a symbolic link
1758 	 *
1759 	 * @param path
1760 	 *            a {@link java.io.File} object.
1761 	 * @param target
1762 	 *            target path of the symlink
1763 	 * @throws java.io.IOException
1764 	 * @since 3.0
1765 	 */
1766 	public void createSymLink(File path, String target) throws IOException {
1767 		FileUtils.createSymLink(path, target);
1768 	}
1769 
1770 	/**
1771 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
1772 	 * of this class may take care to provide a safe implementation for this
1773 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
1774 	 *
1775 	 * @param path
1776 	 *            the file to be created
1777 	 * @return <code>true</code> if the file was created, <code>false</code> if
1778 	 *         the file already existed
1779 	 * @throws java.io.IOException
1780 	 * @deprecated use {@link #createNewFileAtomic(File)} instead
1781 	 * @since 4.5
1782 	 */
1783 	@Deprecated
1784 	public boolean createNewFile(File path) throws IOException {
1785 		return path.createNewFile();
1786 	}
1787 
1788 	/**
1789 	 * A token representing a file created by
1790 	 * {@link #createNewFileAtomic(File)}. The token must be retained until the
1791 	 * file has been deleted in order to guarantee that the unique file was
1792 	 * created atomically. As soon as the file is no longer needed the lock
1793 	 * token must be closed.
1794 	 *
1795 	 * @since 4.7
1796 	 */
1797 	public static class LockToken implements Closeable {
1798 		private boolean isCreated;
1799 
1800 		private Optional<Path> link;
1801 
1802 		LockToken(boolean isCreated, Optional<Path> link) {
1803 			this.isCreated = isCreated;
1804 			this.link = link;
1805 		}
1806 
1807 		/**
1808 		 * @return {@code true} if the file was created successfully
1809 		 */
1810 		public boolean isCreated() {
1811 			return isCreated;
1812 		}
1813 
1814 		@Override
1815 		public void close() {
1816 			if (!link.isPresent()) {
1817 				return;
1818 			}
1819 			Path p = link.get();
1820 			if (!Files.exists(p)) {
1821 				return;
1822 			}
1823 			try {
1824 				Files.delete(p);
1825 			} catch (IOException e) {
1826 				LOG.error(MessageFormat
1827 						.format(JGitText.get().closeLockTokenFailed, this), e);
1828 			}
1829 		}
1830 
1831 		@Override
1832 		public String toString() {
1833 			return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
1834 					", link=" //$NON-NLS-1$
1835 					+ (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
1836 							: "<null>]"); //$NON-NLS-1$
1837 		}
1838 	}
1839 
1840 	/**
1841 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
1842 	 * of this class may take care to provide a safe implementation for this
1843 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
1844 	 *
1845 	 * @param path
1846 	 *            the file to be created
1847 	 * @return LockToken this token must be closed after the created file was
1848 	 *         deleted
1849 	 * @throws IOException
1850 	 * @since 4.7
1851 	 */
1852 	public LockToken createNewFileAtomic(File path) throws IOException {
1853 		return new LockToken(path.createNewFile(), Optional.empty());
1854 	}
1855 
1856 	/**
1857 	 * See
1858 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
1859 	 *
1860 	 * @param base
1861 	 *            The path against which <code>other</code> should be
1862 	 *            relativized.
1863 	 * @param other
1864 	 *            The path that will be made relative to <code>base</code>.
1865 	 * @return A relative path that, when resolved against <code>base</code>,
1866 	 *         will yield the original <code>other</code>.
1867 	 * @see FileUtils#relativizePath(String, String, String, boolean)
1868 	 * @since 3.7
1869 	 */
1870 	public String relativize(String base, String other) {
1871 		return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
1872 	}
1873 
1874 	/**
1875 	 * Enumerates children of a directory.
1876 	 *
1877 	 * @param directory
1878 	 *            to get the children of
1879 	 * @param fileModeStrategy
1880 	 *            to use to calculate the git mode of a child
1881 	 * @return an array of entries for the children
1882 	 *
1883 	 * @since 5.0
1884 	 */
1885 	public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1886 		File[] all = directory.listFiles();
1887 		if (all == null) {
1888 			return NO_ENTRIES;
1889 		}
1890 		Entry[] result = new Entry[all.length];
1891 		for (int i = 0; i < result.length; i++) {
1892 			result[i] = new FileEntry(all[i], this, fileModeStrategy);
1893 		}
1894 		return result;
1895 	}
1896 
1897 	/**
1898 	 * Checks whether the given hook is defined for the given repository, then
1899 	 * runs it with the given arguments.
1900 	 * <p>
1901 	 * The hook's standard output and error streams will be redirected to
1902 	 * <code>System.out</code> and <code>System.err</code> respectively. The
1903 	 * hook will have no stdin.
1904 	 * </p>
1905 	 *
1906 	 * @param repository
1907 	 *            The repository for which a hook should be run.
1908 	 * @param hookName
1909 	 *            The name of the hook to be executed.
1910 	 * @param args
1911 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1912 	 *            but can be an empty array.
1913 	 * @return The ProcessResult describing this hook's execution.
1914 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1915 	 *             if we fail to run the hook somehow. Causes may include an
1916 	 *             interrupted process or I/O errors.
1917 	 * @since 4.0
1918 	 */
1919 	public ProcessResult runHookIfPresent(Repository repository,
1920 			String hookName, String[] args) throws JGitInternalException {
1921 		return runHookIfPresent(repository, hookName, args, System.out,
1922 				System.err, null);
1923 	}
1924 
1925 	/**
1926 	 * Checks whether the given hook is defined for the given repository, then
1927 	 * runs it with the given arguments.
1928 	 *
1929 	 * @param repository
1930 	 *            The repository for which a hook should be run.
1931 	 * @param hookName
1932 	 *            The name of the hook to be executed.
1933 	 * @param args
1934 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1935 	 *            but can be an empty array.
1936 	 * @param outRedirect
1937 	 *            A print stream on which to redirect the hook's stdout. Can be
1938 	 *            <code>null</code>, in which case the hook's standard output
1939 	 *            will be lost.
1940 	 * @param errRedirect
1941 	 *            A print stream on which to redirect the hook's stderr. Can be
1942 	 *            <code>null</code>, in which case the hook's standard error
1943 	 *            will be lost.
1944 	 * @param stdinArgs
1945 	 *            A string to pass on to the standard input of the hook. May be
1946 	 *            <code>null</code>.
1947 	 * @return The ProcessResult describing this hook's execution.
1948 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1949 	 *             if we fail to run the hook somehow. Causes may include an
1950 	 *             interrupted process or I/O errors.
1951 	 * @since 5.11
1952 	 */
1953 	public ProcessResult runHookIfPresent(Repository repository,
1954 			String hookName, String[] args, OutputStream outRedirect,
1955 			OutputStream errRedirect, String stdinArgs)
1956 			throws JGitInternalException {
1957 		return new ProcessResult(Status.NOT_SUPPORTED);
1958 	}
1959 
1960 	/**
1961 	 * See
1962 	 * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, String)}
1963 	 * . Should only be called by FS supporting shell scripts execution.
1964 	 *
1965 	 * @param repository
1966 	 *            The repository for which a hook should be run.
1967 	 * @param hookName
1968 	 *            The name of the hook to be executed.
1969 	 * @param args
1970 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1971 	 *            but can be an empty array.
1972 	 * @param outRedirect
1973 	 *            A print stream on which to redirect the hook's stdout. Can be
1974 	 *            <code>null</code>, in which case the hook's standard output
1975 	 *            will be lost.
1976 	 * @param errRedirect
1977 	 *            A print stream on which to redirect the hook's stderr. Can be
1978 	 *            <code>null</code>, in which case the hook's standard error
1979 	 *            will be lost.
1980 	 * @param stdinArgs
1981 	 *            A string to pass on to the standard input of the hook. May be
1982 	 *            <code>null</code>.
1983 	 * @return The ProcessResult describing this hook's execution.
1984 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1985 	 *             if we fail to run the hook somehow. Causes may include an
1986 	 *             interrupted process or I/O errors.
1987 	 * @since 5.11
1988 	 */
1989 	protected ProcessResult internalRunHookIfPresent(Repository repository,
1990 			String hookName, String[] args, OutputStream outRedirect,
1991 			OutputStream errRedirect, String stdinArgs)
1992 			throws JGitInternalException {
1993 		File hookFile = findHook(repository, hookName);
1994 		if (hookFile == null || hookName == null) {
1995 			return new ProcessResult(Status.NOT_PRESENT);
1996 		}
1997 
1998 		File runDirectory = getRunDirectory(repository, hookName);
1999 		if (runDirectory == null) {
2000 			return new ProcessResult(Status.NOT_PRESENT);
2001 		}
2002 		String cmd = hookFile.getAbsolutePath();
2003 		ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
2004 		hookProcess.directory(runDirectory.getAbsoluteFile());
2005 		Map<String, String> environment = hookProcess.environment();
2006 		environment.put(Constants.GIT_DIR_KEY,
2007 				repository.getDirectory().getAbsolutePath());
2008 		if (!repository.isBare()) {
2009 			environment.put(Constants.GIT_WORK_TREE_KEY,
2010 					repository.getWorkTree().getAbsolutePath());
2011 		}
2012 		try {
2013 			return new ProcessResult(runProcess(hookProcess, outRedirect,
2014 					errRedirect, stdinArgs), Status.OK);
2015 		} catch (IOException e) {
2016 			throw new JGitInternalException(MessageFormat.format(
2017 					JGitText.get().exceptionCaughtDuringExecutionOfHook,
2018 					hookName), e);
2019 		} catch (InterruptedException e) {
2020 			throw new JGitInternalException(MessageFormat.format(
2021 					JGitText.get().exceptionHookExecutionInterrupted,
2022 							hookName), e);
2023 		}
2024 	}
2025 
2026 	/**
2027 	 * Quote a string (such as a file system path obtained from a Java
2028 	 * {@link File} or {@link Path} object) such that it can be passed as first
2029 	 * argument to {@link #runInShell(String, String[])}.
2030 	 * <p>
2031 	 * This default implementation returns the string unchanged.
2032 	 * </p>
2033 	 *
2034 	 * @param cmd
2035 	 *            the String to quote
2036 	 * @return the quoted string
2037 	 */
2038 	String shellQuote(String cmd) {
2039 		return cmd;
2040 	}
2041 
2042 	/**
2043 	 * Tries to find a hook matching the given one in the given repository.
2044 	 *
2045 	 * @param repository
2046 	 *            The repository within which to find a hook.
2047 	 * @param hookName
2048 	 *            The name of the hook we're trying to find.
2049 	 * @return The {@link java.io.File} containing this particular hook if it
2050 	 *         exists in the given repository, <code>null</code> otherwise.
2051 	 * @since 4.0
2052 	 */
2053 	public File findHook(Repository repository, String hookName) {
2054 		if (hookName == null) {
2055 			return null;
2056 		}
2057 		File hookDir = getHooksDirectory(repository);
2058 		if (hookDir == null) {
2059 			return null;
2060 		}
2061 		File hookFile = new File(hookDir, hookName);
2062 		if (hookFile.isAbsolute()) {
2063 			if (!hookFile.exists() || (FS.DETECTED.supportsExecute()
2064 					&& !FS.DETECTED.canExecute(hookFile))) {
2065 				return null;
2066 			}
2067 		} else {
2068 			try {
2069 				File runDirectory = getRunDirectory(repository, hookName);
2070 				if (runDirectory == null) {
2071 					return null;
2072 				}
2073 				Path hookPath = runDirectory.getAbsoluteFile().toPath()
2074 						.resolve(hookFile.toPath());
2075 				FS fs = repository.getFS();
2076 				if (fs == null) {
2077 					fs = FS.DETECTED;
2078 				}
2079 				if (!Files.exists(hookPath) || (fs.supportsExecute()
2080 						&& !fs.canExecute(hookPath.toFile()))) {
2081 					return null;
2082 				}
2083 				hookFile = hookPath.toFile();
2084 			} catch (InvalidPathException e) {
2085 				LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath,
2086 						hookFile));
2087 				return null;
2088 			}
2089 		}
2090 		return hookFile;
2091 	}
2092 
2093 	private File getRunDirectory(Repository repository,
2094 			@NonNull String hookName) {
2095 		if (repository.isBare()) {
2096 			return repository.getDirectory();
2097 		}
2098 		switch (hookName) {
2099 		case "pre-receive": //$NON-NLS-1$
2100 		case "update": //$NON-NLS-1$
2101 		case "post-receive": //$NON-NLS-1$
2102 		case "post-update": //$NON-NLS-1$
2103 		case "push-to-checkout": //$NON-NLS-1$
2104 			return repository.getDirectory();
2105 		default:
2106 			return repository.getWorkTree();
2107 		}
2108 	}
2109 
2110 	private File getHooksDirectory(Repository repository) {
2111 		Config config = repository.getConfig();
2112 		String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
2113 				null, ConfigConstants.CONFIG_KEY_HOOKS_PATH);
2114 		if (hooksDir != null) {
2115 			return new File(hooksDir);
2116 		}
2117 		File dir = repository.getDirectory();
2118 		return dir == null ? null : new File(dir, Constants.HOOKS);
2119 	}
2120 
2121 	/**
2122 	 * Runs the given process until termination, clearing its stdout and stderr
2123 	 * streams on-the-fly.
2124 	 *
2125 	 * @param processBuilder
2126 	 *            The process builder configured for this process.
2127 	 * @param outRedirect
2128 	 *            A OutputStream on which to redirect the processes stdout. Can
2129 	 *            be <code>null</code>, in which case the processes standard
2130 	 *            output will be lost.
2131 	 * @param errRedirect
2132 	 *            A OutputStream on which to redirect the processes stderr. Can
2133 	 *            be <code>null</code>, in which case the processes standard
2134 	 *            error will be lost.
2135 	 * @param stdinArgs
2136 	 *            A string to pass on to the standard input of the hook. Can be
2137 	 *            <code>null</code>.
2138 	 * @return the exit value of this process.
2139 	 * @throws java.io.IOException
2140 	 *             if an I/O error occurs while executing this process.
2141 	 * @throws java.lang.InterruptedException
2142 	 *             if the current thread is interrupted while waiting for the
2143 	 *             process to end.
2144 	 * @since 4.2
2145 	 */
2146 	public int runProcess(ProcessBuilder processBuilder,
2147 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
2148 			throws IOException, InterruptedException {
2149 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
2150 						stdinArgs.getBytes(UTF_8));
2151 		return runProcess(processBuilder, outRedirect, errRedirect, in);
2152 	}
2153 
2154 	/**
2155 	 * Runs the given process until termination, clearing its stdout and stderr
2156 	 * streams on-the-fly.
2157 	 *
2158 	 * @param processBuilder
2159 	 *            The process builder configured for this process.
2160 	 * @param outRedirect
2161 	 *            An OutputStream on which to redirect the processes stdout. Can
2162 	 *            be <code>null</code>, in which case the processes standard
2163 	 *            output will be lost.
2164 	 * @param errRedirect
2165 	 *            An OutputStream on which to redirect the processes stderr. Can
2166 	 *            be <code>null</code>, in which case the processes standard
2167 	 *            error will be lost.
2168 	 * @param inRedirect
2169 	 *            An InputStream from which to redirect the processes stdin. Can
2170 	 *            be <code>null</code>, in which case the process doesn't get
2171 	 *            any data over stdin. It is assumed that the whole InputStream
2172 	 *            will be consumed by the process. The method will close the
2173 	 *            inputstream after all bytes are read.
2174 	 * @return the return code of this process.
2175 	 * @throws java.io.IOException
2176 	 *             if an I/O error occurs while executing this process.
2177 	 * @throws java.lang.InterruptedException
2178 	 *             if the current thread is interrupted while waiting for the
2179 	 *             process to end.
2180 	 * @since 4.2
2181 	 */
2182 	public int runProcess(ProcessBuilder processBuilder,
2183 			OutputStream outRedirect, OutputStream errRedirect,
2184 			InputStream inRedirect) throws IOException,
2185 			InterruptedException {
2186 		ExecutorService executor = Executors.newFixedThreadPool(2);
2187 		Process process = null;
2188 		// We'll record the first I/O exception that occurs, but keep on trying
2189 		// to dispose of our open streams and file handles
2190 		IOException ioException = null;
2191 		try {
2192 			process = processBuilder.start();
2193 			executor.execute(
2194 					new StreamGobbler(process.getErrorStream(), errRedirect));
2195 			executor.execute(
2196 					new StreamGobbler(process.getInputStream(), outRedirect));
2197 			@SuppressWarnings("resource") // Closed in the finally block
2198 			OutputStream outputStream = process.getOutputStream();
2199 			try {
2200 				if (inRedirect != null) {
2201 					new StreamGobbler(inRedirect, outputStream).copy();
2202 				}
2203 			} finally {
2204 				try {
2205 					outputStream.close();
2206 				} catch (IOException e) {
2207 					// When the process exits before consuming the input, the OutputStream
2208 					// is replaced with the null output stream. This null output stream
2209 					// throws IOException for all write calls. When StreamGobbler fails to
2210 					// flush the buffer because of this, this close call tries to flush it
2211 					// again. This causes another IOException. Since we ignore the
2212 					// IOException in StreamGobbler, we also ignore the exception here.
2213 				}
2214 			}
2215 			return process.waitFor();
2216 		} catch (IOException e) {
2217 			ioException = e;
2218 		} finally {
2219 			shutdownAndAwaitTermination(executor);
2220 			if (process != null) {
2221 				try {
2222 					process.waitFor();
2223 				} catch (InterruptedException e) {
2224 					// Thrown by the outer try.
2225 					// Swallow this one to carry on our cleanup, and clear the
2226 					// interrupted flag (processes throw the exception without
2227 					// clearing the flag).
2228 					Thread.interrupted();
2229 				}
2230 				// A process doesn't clean its own resources even when destroyed
2231 				// Explicitly try and close all three streams, preserving the
2232 				// outer I/O exception if any.
2233 				if (inRedirect != null) {
2234 					inRedirect.close();
2235 				}
2236 				try {
2237 					process.getErrorStream().close();
2238 				} catch (IOException e) {
2239 					ioException = ioException != null ? ioException : e;
2240 				}
2241 				try {
2242 					process.getInputStream().close();
2243 				} catch (IOException e) {
2244 					ioException = ioException != null ? ioException : e;
2245 				}
2246 				try {
2247 					process.getOutputStream().close();
2248 				} catch (IOException e) {
2249 					ioException = ioException != null ? ioException : e;
2250 				}
2251 				process.destroy();
2252 			}
2253 		}
2254 		// We can only be here if the outer try threw an IOException.
2255 		throw ioException;
2256 	}
2257 
2258 	/**
2259 	 * Shuts down an {@link ExecutorService} in two phases, first by calling
2260 	 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
2261 	 * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
2262 	 * necessary, to cancel any lingering tasks. Returns true if the pool has
2263 	 * been properly shutdown, false otherwise.
2264 	 * <p>
2265 	 *
2266 	 * @param pool
2267 	 *            the pool to shutdown
2268 	 * @return <code>true</code> if the pool has been properly shutdown,
2269 	 *         <code>false</code> otherwise.
2270 	 */
2271 	private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
2272 		boolean hasShutdown = true;
2273 		pool.shutdown(); // Disable new tasks from being submitted
2274 		try {
2275 			// Wait a while for existing tasks to terminate
2276 			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
2277 				pool.shutdownNow(); // Cancel currently executing tasks
2278 				// Wait a while for tasks to respond to being canceled
2279 				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
2280 					hasShutdown = false;
2281 			}
2282 		} catch (InterruptedException ie) {
2283 			// (Re-)Cancel if current thread also interrupted
2284 			pool.shutdownNow();
2285 			// Preserve interrupt status
2286 			Thread.currentThread().interrupt();
2287 			hasShutdown = false;
2288 		}
2289 		return hasShutdown;
2290 	}
2291 
2292 	/**
2293 	 * Initialize a ProcessBuilder to run a command using the system shell.
2294 	 *
2295 	 * @param cmd
2296 	 *            command to execute. This string should originate from the
2297 	 *            end-user, and thus is platform specific.
2298 	 * @param args
2299 	 *            arguments to pass to command. These should be protected from
2300 	 *            shell evaluation.
2301 	 * @return a partially completed process builder. Caller should finish
2302 	 *         populating directory, environment, and then start the process.
2303 	 */
2304 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
2305 
2306 	/**
2307 	 * Execute a command defined by a {@link java.lang.ProcessBuilder}.
2308 	 *
2309 	 * @param pb
2310 	 *            The command to be executed
2311 	 * @param in
2312 	 *            The standard input stream passed to the process
2313 	 * @return The result of the executed command
2314 	 * @throws java.lang.InterruptedException
2315 	 * @throws java.io.IOException
2316 	 * @since 4.2
2317 	 */
2318 	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
2319 			throws IOException, InterruptedException {
2320 		try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
2321 				TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
2322 						1024 * 1024)) {
2323 			int rc = runProcess(pb, stdout, stderr, in);
2324 			return new ExecutionResult(stdout, stderr, rc);
2325 		}
2326 	}
2327 
2328 	private static class Holder<V> {
2329 		final V value;
2330 
2331 		Holder(V value) {
2332 			this.value = value;
2333 		}
2334 	}
2335 
2336 	/**
2337 	 * File attributes we typically care for.
2338 	 *
2339 	 * @since 3.3
2340 	 */
2341 	public static class Attributes {
2342 
2343 		/**
2344 		 * @return true if this are the attributes of a directory
2345 		 */
2346 		public boolean isDirectory() {
2347 			return isDirectory;
2348 		}
2349 
2350 		/**
2351 		 * @return true if this are the attributes of an executable file
2352 		 */
2353 		public boolean isExecutable() {
2354 			return isExecutable;
2355 		}
2356 
2357 		/**
2358 		 * @return true if this are the attributes of a symbolic link
2359 		 */
2360 		public boolean isSymbolicLink() {
2361 			return isSymbolicLink;
2362 		}
2363 
2364 		/**
2365 		 * @return true if this are the attributes of a regular file
2366 		 */
2367 		public boolean isRegularFile() {
2368 			return isRegularFile;
2369 		}
2370 
2371 		/**
2372 		 * @return the time when the file was created
2373 		 */
2374 		public long getCreationTime() {
2375 			return creationTime;
2376 		}
2377 
2378 		/**
2379 		 * @return the time (milliseconds since 1970-01-01) when this object was
2380 		 *         last modified
2381 		 * @deprecated use getLastModifiedInstant instead
2382 		 */
2383 		@Deprecated
2384 		public long getLastModifiedTime() {
2385 			return lastModifiedInstant.toEpochMilli();
2386 		}
2387 
2388 		/**
2389 		 * @return the time when this object was last modified
2390 		 * @since 5.1.9
2391 		 */
2392 		public Instant getLastModifiedInstant() {
2393 			return lastModifiedInstant;
2394 		}
2395 
2396 		private final boolean isDirectory;
2397 
2398 		private final boolean isSymbolicLink;
2399 
2400 		private final boolean isRegularFile;
2401 
2402 		private final long creationTime;
2403 
2404 		private final Instant lastModifiedInstant;
2405 
2406 		private final boolean isExecutable;
2407 
2408 		private final File file;
2409 
2410 		private final boolean exists;
2411 
2412 		/**
2413 		 * file length
2414 		 */
2415 		protected long length = -1;
2416 
2417 		final FS fs;
2418 
2419 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
2420 				boolean isExecutable, boolean isSymbolicLink,
2421 				boolean isRegularFile, long creationTime,
2422 				Instant lastModifiedInstant, long length) {
2423 			this.fs = fs;
2424 			this.file = file;
2425 			this.exists = exists;
2426 			this.isDirectory = isDirectory;
2427 			this.isExecutable = isExecutable;
2428 			this.isSymbolicLink = isSymbolicLink;
2429 			this.isRegularFile = isRegularFile;
2430 			this.creationTime = creationTime;
2431 			this.lastModifiedInstant = lastModifiedInstant;
2432 			this.length = length;
2433 		}
2434 
2435 		/**
2436 		 * Constructor when there are issues with reading. All attributes except
2437 		 * given will be set to the default values.
2438 		 *
2439 		 * @param fs
2440 		 * @param path
2441 		 */
2442 		public Attributes(File path, FS fs) {
2443 			this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
2444 		}
2445 
2446 		/**
2447 		 * @return length of this file object
2448 		 */
2449 		public long getLength() {
2450 			if (length == -1)
2451 				return length = file.length();
2452 			return length;
2453 		}
2454 
2455 		/**
2456 		 * @return the filename
2457 		 */
2458 		public String getName() {
2459 			return file.getName();
2460 		}
2461 
2462 		/**
2463 		 * @return the file the attributes apply to
2464 		 */
2465 		public File getFile() {
2466 			return file;
2467 		}
2468 
2469 		boolean exists() {
2470 			return exists;
2471 		}
2472 	}
2473 
2474 	/**
2475 	 * Get the file attributes we care for.
2476 	 *
2477 	 * @param path
2478 	 *            a {@link java.io.File} object.
2479 	 * @return the file attributes we care for.
2480 	 * @since 3.3
2481 	 */
2482 	public Attributes getAttributes(File path) {
2483 		boolean isDirectory = isDirectory(path);
2484 		boolean isFile = !isDirectory && path.isFile();
2485 		assert path.exists() == isDirectory || isFile;
2486 		boolean exists = isDirectory || isFile;
2487 		boolean canExecute = exists && !isDirectory && canExecute(path);
2488 		boolean isSymlink = false;
2489 		Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
2490 		long createTime = 0L;
2491 		return new Attributes(this, path, exists, isDirectory, canExecute,
2492 				isSymlink, isFile, createTime, lastModified, -1);
2493 	}
2494 
2495 	/**
2496 	 * Normalize the unicode path to composed form.
2497 	 *
2498 	 * @param file
2499 	 *            a {@link java.io.File} object.
2500 	 * @return NFC-format File
2501 	 * @since 3.3
2502 	 */
2503 	public File normalize(File file) {
2504 		return file;
2505 	}
2506 
2507 	/**
2508 	 * Normalize the unicode path to composed form.
2509 	 *
2510 	 * @param name
2511 	 *            path name
2512 	 * @return NFC-format string
2513 	 * @since 3.3
2514 	 */
2515 	public String normalize(String name) {
2516 		return name;
2517 	}
2518 
2519 	/**
2520 	 * This runnable will consume an input stream's content into an output
2521 	 * stream as soon as it gets available.
2522 	 * <p>
2523 	 * Typically used to empty processes' standard output and error, preventing
2524 	 * them to choke.
2525 	 * </p>
2526 	 * <p>
2527 	 * <b>Note</b> that a {@link StreamGobbler} will never close either of its
2528 	 * streams.
2529 	 * </p>
2530 	 */
2531 	private static class StreamGobbler implements Runnable {
2532 		private InputStream in;
2533 
2534 		private OutputStream out;
2535 
2536 		public StreamGobbler(InputStream stream, OutputStream output) {
2537 			this.in = stream;
2538 			this.out = output;
2539 		}
2540 
2541 		@Override
2542 		public void run() {
2543 			try {
2544 				copy();
2545 			} catch (IOException e) {
2546 				// Do nothing on read failure; leave streams open.
2547 			}
2548 		}
2549 
2550 		void copy() throws IOException {
2551 			boolean writeFailure = false;
2552 			byte[] buffer = new byte[4096];
2553 			int readBytes;
2554 			while ((readBytes = in.read(buffer)) != -1) {
2555 				// Do not try to write again after a failure, but keep
2556 				// reading as long as possible to prevent the input stream
2557 				// from choking.
2558 				if (!writeFailure && out != null) {
2559 					try {
2560 						out.write(buffer, 0, readBytes);
2561 						out.flush();
2562 					} catch (IOException e) {
2563 						writeFailure = true;
2564 					}
2565 				}
2566 			}
2567 		}
2568 	}
2569 }