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