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