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