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.
1084 	 * <p>
1085 	 * For symlinks it sets the modified time of the link target.
1086 	 *
1087 	 * @param f
1088 	 *            a {@link java.io.File} object.
1089 	 * @param time
1090 	 *            last modified time
1091 	 * @throws java.io.IOException
1092 	 * @since 3.0
1093 	 * @deprecated use {@link #setLastModified(Path, Instant)} instead
1094 	 */
1095 	@Deprecated
1096 	public void setLastModified(File f, long time) throws IOException {
1097 		FileUtils.setLastModified(f, time);
1098 	}
1099 
1100 	/**
1101 	 * Set the last modified time of a file system object.
1102 	 * <p>
1103 	 * For symlinks it sets the modified time of the link target.
1104 	 *
1105 	 * @param p
1106 	 *            a {@link Path} object.
1107 	 * @param time
1108 	 *            last modified time
1109 	 * @throws java.io.IOException
1110 	 * @since 5.1.9
1111 	 */
1112 	public void setLastModified(Path p, Instant time) throws IOException {
1113 		FileUtils.setLastModified(p, time);
1114 	}
1115 
1116 	/**
1117 	 * Get the length of a file or link, If the OS/JRE supports symbolic links
1118 	 * it's the length of the link, else the length of the target.
1119 	 *
1120 	 * @param path
1121 	 *            a {@link java.io.File} object.
1122 	 * @return length of a file
1123 	 * @throws java.io.IOException
1124 	 * @since 3.0
1125 	 */
1126 	public long length(File path) throws IOException {
1127 		return FileUtils.getLength(path);
1128 	}
1129 
1130 	/**
1131 	 * Delete a file. Throws an exception if delete fails.
1132 	 *
1133 	 * @param f
1134 	 *            a {@link java.io.File} object.
1135 	 * @throws java.io.IOException
1136 	 *             this may be a Java7 subclass with detailed information
1137 	 * @since 3.3
1138 	 */
1139 	public void delete(File f) throws IOException {
1140 		FileUtils.delete(f);
1141 	}
1142 
1143 	/**
1144 	 * Resolve this file to its actual path name that the JRE can use.
1145 	 * <p>
1146 	 * This method can be relatively expensive. Computing a translation may
1147 	 * require forking an external process per path name translated. Callers
1148 	 * should try to minimize the number of translations necessary by caching
1149 	 * the results.
1150 	 * <p>
1151 	 * Not all platforms and JREs require path name translation. Currently only
1152 	 * Cygwin on Win32 require translation for Cygwin based paths.
1153 	 *
1154 	 * @param dir
1155 	 *            directory relative to which the path name is.
1156 	 * @param name
1157 	 *            path name to translate.
1158 	 * @return the translated path. <code>new File(dir,name)</code> if this
1159 	 *         platform does not require path name translation.
1160 	 */
1161 	public File resolve(File dir, String name) {
1162 		final File abspn = new File(name);
1163 		if (abspn.isAbsolute())
1164 			return abspn;
1165 		return new File(dir, name);
1166 	}
1167 
1168 	/**
1169 	 * Determine the user's home directory (location where preferences are).
1170 	 * <p>
1171 	 * This method can be expensive on the first invocation if path name
1172 	 * translation is required. Subsequent invocations return a cached result.
1173 	 * <p>
1174 	 * Not all platforms and JREs require path name translation. Currently only
1175 	 * Cygwin on Win32 requires translation of the Cygwin HOME directory.
1176 	 *
1177 	 * @return the user's home directory; null if the user does not have one.
1178 	 */
1179 	public File userHome() {
1180 		Holder<File> p = userHome;
1181 		if (p == null) {
1182 			p = new Holder<>(safeUserHomeImpl());
1183 			userHome = p;
1184 		}
1185 		return p.value;
1186 	}
1187 
1188 	private File safeUserHomeImpl() {
1189 		File home;
1190 		try {
1191 			home = userHomeImpl();
1192 			if (home != null) {
1193 				home.toPath();
1194 				return home;
1195 			}
1196 		} catch (RuntimeException e) {
1197 			LOG.error(JGitText.get().exceptionWhileFindingUserHome, e);
1198 		}
1199 		home = defaultUserHomeImpl();
1200 		if (home != null) {
1201 			try {
1202 				home.toPath();
1203 				return home;
1204 			} catch (InvalidPathException e) {
1205 				LOG.error(MessageFormat
1206 						.format(JGitText.get().invalidHomeDirectory, home), e);
1207 			}
1208 		}
1209 		return null;
1210 	}
1211 
1212 	/**
1213 	 * Set the user's home directory location.
1214 	 *
1215 	 * @param path
1216 	 *            the location of the user's preferences; null if there is no
1217 	 *            home directory for the current user.
1218 	 * @return {@code this}.
1219 	 */
1220 	public FS setUserHome(File path) {
1221 		userHome = new Holder<>(path);
1222 		return this;
1223 	}
1224 
1225 	/**
1226 	 * Does this file system have problems with atomic renames?
1227 	 *
1228 	 * @return true if the caller should retry a failed rename of a lock file.
1229 	 */
1230 	public abstract boolean retryFailedLockFileCommit();
1231 
1232 	/**
1233 	 * Return all the attributes of a file, without following symbolic links.
1234 	 *
1235 	 * @param file
1236 	 * @return {@link BasicFileAttributes} of the file
1237 	 * @throws IOException in case of any I/O errors accessing the file
1238 	 *
1239 	 * @since 4.5.6
1240 	 */
1241 	public BasicFileAttributes fileAttributes(File file) throws IOException {
1242 		return FileUtils.fileAttributes(file);
1243 	}
1244 
1245 	/**
1246 	 * Determine the user's home directory (location where preferences are).
1247 	 *
1248 	 * @return the user's home directory; null if the user does not have one.
1249 	 */
1250 	protected File userHomeImpl() {
1251 		return defaultUserHomeImpl();
1252 	}
1253 
1254 	private File defaultUserHomeImpl() {
1255 		final String home = AccessController.doPrivileged(
1256 				(PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
1257 		);
1258 		if (home == null || home.length() == 0)
1259 			return null;
1260 		return new File(home).getAbsoluteFile();
1261 	}
1262 
1263 	/**
1264 	 * Searches the given path to see if it contains one of the given files.
1265 	 * Returns the first it finds. Returns null if not found or if path is null.
1266 	 *
1267 	 * @param path
1268 	 *            List of paths to search separated by File.pathSeparator
1269 	 * @param lookFor
1270 	 *            Files to search for in the given path
1271 	 * @return the first match found, or null
1272 	 * @since 3.0
1273 	 */
1274 	protected static File searchPath(String path, String... lookFor) {
1275 		if (path == null)
1276 			return null;
1277 
1278 		for (String p : path.split(File.pathSeparator)) {
1279 			for (String command : lookFor) {
1280 				final File file = new File(p, command);
1281 				try {
1282 					if (file.isFile()) {
1283 						return file.getAbsoluteFile();
1284 					}
1285 				} catch (SecurityException e) {
1286 					LOG.warn(MessageFormat.format(
1287 							JGitText.get().skipNotAccessiblePath,
1288 							file.getPath()));
1289 				}
1290 			}
1291 		}
1292 		return null;
1293 	}
1294 
1295 	/**
1296 	 * Execute a command and return a single line of output as a String
1297 	 *
1298 	 * @param dir
1299 	 *            Working directory for the command
1300 	 * @param command
1301 	 *            as component array
1302 	 * @param encoding
1303 	 *            to be used to parse the command's output
1304 	 * @return the one-line output of the command or {@code null} if there is
1305 	 *         none
1306 	 * @throws org.eclipse.jgit.errors.CommandFailedException
1307 	 *             thrown when the command failed (return code was non-zero)
1308 	 */
1309 	@Nullable
1310 	protected static String readPipe(File dir, String[] command,
1311 			String encoding) throws CommandFailedException {
1312 		return readPipe(dir, command, encoding, null);
1313 	}
1314 
1315 	/**
1316 	 * Execute a command and return a single line of output as a String
1317 	 *
1318 	 * @param dir
1319 	 *            Working directory for the command
1320 	 * @param command
1321 	 *            as component array
1322 	 * @param encoding
1323 	 *            to be used to parse the command's output
1324 	 * @param env
1325 	 *            Map of environment variables to be merged with those of the
1326 	 *            current process
1327 	 * @return the one-line output of the command or {@code null} if there is
1328 	 *         none
1329 	 * @throws org.eclipse.jgit.errors.CommandFailedException
1330 	 *             thrown when the command failed (return code was non-zero)
1331 	 * @since 4.0
1332 	 */
1333 	@Nullable
1334 	protected static String readPipe(File dir, String[] command,
1335 			String encoding, Map<String, String> env)
1336 			throws CommandFailedException {
1337 		final boolean debug = LOG.isDebugEnabled();
1338 		try {
1339 			if (debug) {
1340 				LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
1341 						+ dir);
1342 			}
1343 			ProcessBuilder pb = new ProcessBuilder(command);
1344 			pb.directory(dir);
1345 			if (env != null) {
1346 				pb.environment().putAll(env);
1347 			}
1348 			Process p;
1349 			try {
1350 				p = pb.start();
1351 			} catch (IOException e) {
1352 				// Process failed to start
1353 				throw new CommandFailedException(-1, e.getMessage(), e);
1354 			}
1355 			p.getOutputStream().close();
1356 			GobblerThread gobbler = new GobblerThread(p, command, dir);
1357 			gobbler.start();
1358 			String r = null;
1359 			try (BufferedReader lineRead = new BufferedReader(
1360 					new InputStreamReader(p.getInputStream(), encoding))) {
1361 				r = lineRead.readLine();
1362 				if (debug) {
1363 					LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
1364 					LOG.debug("remaining output:\n"); //$NON-NLS-1$
1365 					String l;
1366 					while ((l = lineRead.readLine()) != null) {
1367 						LOG.debug(l);
1368 					}
1369 				}
1370 			}
1371 
1372 			for (;;) {
1373 				try {
1374 					int rc = p.waitFor();
1375 					gobbler.join();
1376 					if (rc == 0 && !gobbler.fail.get()) {
1377 						return r;
1378 					}
1379 					if (debug) {
1380 						LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
1381 					}
1382 					throw new CommandFailedException(rc,
1383 							gobbler.errorMessage.get(),
1384 							gobbler.exception.get());
1385 				} catch (InterruptedException ie) {
1386 					// Stop bothering me, I have a zombie to reap.
1387 				}
1388 			}
1389 		} catch (IOException e) {
1390 			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
1391 		} catch (AccessControlException e) {
1392 			LOG.warn(MessageFormat.format(
1393 					JGitText.get().readPipeIsNotAllowedRequiredPermission,
1394 					command, dir, e.getPermission()));
1395 		} catch (SecurityException e) {
1396 			LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
1397 					command, dir));
1398 		}
1399 		if (debug) {
1400 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
1401 		}
1402 		return null;
1403 	}
1404 
1405 	private static class GobblerThread extends Thread {
1406 
1407 		/* The process has 5 seconds to exit after closing stderr */
1408 		private static final int PROCESS_EXIT_TIMEOUT = 5;
1409 
1410 		private final Process p;
1411 		private final String desc;
1412 		private final String dir;
1413 		final AtomicBoolean fail = new AtomicBoolean();
1414 		final AtomicReference<String> errorMessage = new AtomicReference<>();
1415 		final AtomicReference<Throwable> exception = new AtomicReference<>();
1416 
1417 		GobblerThread(Process p, String[] command, File dir) {
1418 			this.p = p;
1419 			this.desc = Arrays.toString(command);
1420 			this.dir = Objects.toString(dir);
1421 		}
1422 
1423 		@Override
1424 		public void run() {
1425 			StringBuilder err = new StringBuilder();
1426 			try (InputStream is = p.getErrorStream()) {
1427 				int ch;
1428 				while ((ch = is.read()) != -1) {
1429 					err.append((char) ch);
1430 				}
1431 			} catch (IOException e) {
1432 				if (waitForProcessCompletion(e) && p.exitValue() != 0) {
1433 					setError(e, e.getMessage(), p.exitValue());
1434 					fail.set(true);
1435 				} else {
1436 					// ignore. command terminated faster and stream was just closed
1437 					// or the process didn't terminate within timeout
1438 				}
1439 			} finally {
1440 				if (waitForProcessCompletion(null) && err.length() > 0) {
1441 					setError(null, err.toString(), p.exitValue());
1442 					if (p.exitValue() != 0) {
1443 						fail.set(true);
1444 					}
1445 				}
1446 			}
1447 		}
1448 
1449 		@SuppressWarnings("boxing")
1450 		private boolean waitForProcessCompletion(IOException originalError) {
1451 			try {
1452 				if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
1453 					setError(originalError, MessageFormat.format(
1454 							JGitText.get().commandClosedStderrButDidntExit,
1455 							desc, PROCESS_EXIT_TIMEOUT), -1);
1456 					fail.set(true);
1457 					return false;
1458 				}
1459 			} catch (InterruptedException e) {
1460 				setError(originalError, MessageFormat.format(
1461 						JGitText.get().threadInterruptedWhileRunning, desc), -1);
1462 				fail.set(true);
1463 				return false;
1464 			}
1465 			return true;
1466 		}
1467 
1468 		private void setError(IOException e, String message, int exitCode) {
1469 			exception.set(e);
1470 			errorMessage.set(MessageFormat.format(
1471 					JGitText.get().exceptionCaughtDuringExecutionOfCommand,
1472 					desc, dir, Integer.valueOf(exitCode), message));
1473 		}
1474 	}
1475 
1476 	/**
1477 	 * Discover the path to the Git executable.
1478 	 *
1479 	 * @return the path to the Git executable or {@code null} if it cannot be
1480 	 *         determined.
1481 	 * @since 4.0
1482 	 */
1483 	protected abstract File discoverGitExe();
1484 
1485 	/**
1486 	 * Discover the path to the system-wide Git configuration file
1487 	 *
1488 	 * @return the path to the system-wide Git configuration file or
1489 	 *         {@code null} if it cannot be determined.
1490 	 * @since 4.0
1491 	 */
1492 	protected File discoverGitSystemConfig() {
1493 		File gitExe = discoverGitExe();
1494 		if (gitExe == null) {
1495 			return null;
1496 		}
1497 
1498 		// Bug 480782: Check if the discovered git executable is JGit CLI
1499 		String v;
1500 		try {
1501 			v = readPipe(gitExe.getParentFile(),
1502 					new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$
1503 				Charset.defaultCharset().name());
1504 		} catch (CommandFailedException e) {
1505 			LOG.warn(e.getMessage());
1506 			return null;
1507 		}
1508 		if (StringUtils.isEmptyOrNull(v)
1509 				|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
1510 			return null;
1511 		}
1512 
1513 		// Trick Git into printing the path to the config file by using "echo"
1514 		// as the editor.
1515 		Map<String, String> env = new HashMap<>();
1516 		env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
1517 
1518 		String w;
1519 		try {
1520 			w = readPipe(gitExe.getParentFile(),
1521 					new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
1522 							"--edit" }, //$NON-NLS-1$
1523 				Charset.defaultCharset().name(), env);
1524 		} catch (CommandFailedException e) {
1525 			LOG.warn(e.getMessage());
1526 			return null;
1527 		}
1528 		if (StringUtils.isEmptyOrNull(w)) {
1529 			return null;
1530 		}
1531 
1532 		return new File(w);
1533 	}
1534 
1535 	/**
1536 	 * Get the currently used path to the system-wide Git configuration file.
1537 	 *
1538 	 * @return the currently used path to the system-wide Git configuration file
1539 	 *         or {@code null} if none has been set.
1540 	 * @since 4.0
1541 	 */
1542 	public File getGitSystemConfig() {
1543 		if (gitSystemConfig == null) {
1544 			gitSystemConfig = new Holder<>(discoverGitSystemConfig());
1545 		}
1546 		return gitSystemConfig.value;
1547 	}
1548 
1549 	/**
1550 	 * Set the path to the system-wide Git configuration file to use.
1551 	 *
1552 	 * @param configFile
1553 	 *            the path to the config file.
1554 	 * @return {@code this}
1555 	 * @since 4.0
1556 	 */
1557 	public FS setGitSystemConfig(File configFile) {
1558 		gitSystemConfig = new Holder<>(configFile);
1559 		return this;
1560 	}
1561 
1562 	/**
1563 	 * Get the parent directory of this file's parent directory
1564 	 *
1565 	 * @param grandchild
1566 	 *            a {@link java.io.File} object.
1567 	 * @return the parent directory of this file's parent directory or
1568 	 *         {@code null} in case there's no grandparent directory
1569 	 * @since 4.0
1570 	 */
1571 	protected static File resolveGrandparentFile(File grandchild) {
1572 		if (grandchild != null) {
1573 			File parent = grandchild.getParentFile();
1574 			if (parent != null)
1575 				return parent.getParentFile();
1576 		}
1577 		return null;
1578 	}
1579 
1580 	/**
1581 	 * Check if a file is a symbolic link and read it
1582 	 *
1583 	 * @param path
1584 	 *            a {@link java.io.File} object.
1585 	 * @return target of link or null
1586 	 * @throws java.io.IOException
1587 	 * @since 3.0
1588 	 */
1589 	public String readSymLink(File path) throws IOException {
1590 		return FileUtils.readSymLink(path);
1591 	}
1592 
1593 	/**
1594 	 * Whether the path is a symbolic link (and we support these).
1595 	 *
1596 	 * @param path
1597 	 *            a {@link java.io.File} object.
1598 	 * @return true if the path is a symbolic link (and we support these)
1599 	 * @throws java.io.IOException
1600 	 * @since 3.0
1601 	 */
1602 	public boolean isSymLink(File path) throws IOException {
1603 		return FileUtils.isSymlink(path);
1604 	}
1605 
1606 	/**
1607 	 * Tests if the path exists, in case of a symbolic link, true even if the
1608 	 * target does not exist
1609 	 *
1610 	 * @param path
1611 	 *            a {@link java.io.File} object.
1612 	 * @return true if path exists
1613 	 * @since 3.0
1614 	 */
1615 	public boolean exists(File path) {
1616 		return FileUtils.exists(path);
1617 	}
1618 
1619 	/**
1620 	 * Check if path is a directory. If the OS/JRE supports symbolic links and
1621 	 * path is a symbolic link to a directory, this method returns false.
1622 	 *
1623 	 * @param path
1624 	 *            a {@link java.io.File} object.
1625 	 * @return true if file is a directory,
1626 	 * @since 3.0
1627 	 */
1628 	public boolean isDirectory(File path) {
1629 		return FileUtils.isDirectory(path);
1630 	}
1631 
1632 	/**
1633 	 * Examine if path represents a regular file. If the OS/JRE supports
1634 	 * symbolic links the test returns false if path represents a symbolic link.
1635 	 *
1636 	 * @param path
1637 	 *            a {@link java.io.File} object.
1638 	 * @return true if path represents a regular file
1639 	 * @since 3.0
1640 	 */
1641 	public boolean isFile(File path) {
1642 		return FileUtils.isFile(path);
1643 	}
1644 
1645 	/**
1646 	 * Whether path is hidden, either starts with . on unix or has the hidden
1647 	 * attribute in windows
1648 	 *
1649 	 * @param path
1650 	 *            a {@link java.io.File} object.
1651 	 * @return true if path is hidden, either starts with . on unix or has the
1652 	 *         hidden attribute in windows
1653 	 * @throws java.io.IOException
1654 	 * @since 3.0
1655 	 */
1656 	public boolean isHidden(File path) throws IOException {
1657 		return FileUtils.isHidden(path);
1658 	}
1659 
1660 	/**
1661 	 * Set the hidden attribute for file whose name starts with a period.
1662 	 *
1663 	 * @param path
1664 	 *            a {@link java.io.File} object.
1665 	 * @param hidden
1666 	 *            whether to set the file hidden
1667 	 * @throws java.io.IOException
1668 	 * @since 3.0
1669 	 */
1670 	public void setHidden(File path, boolean hidden) throws IOException {
1671 		FileUtils.setHidden(path, hidden);
1672 	}
1673 
1674 	/**
1675 	 * Create a symbolic link
1676 	 *
1677 	 * @param path
1678 	 *            a {@link java.io.File} object.
1679 	 * @param target
1680 	 *            target path of the symlink
1681 	 * @throws java.io.IOException
1682 	 * @since 3.0
1683 	 */
1684 	public void createSymLink(File path, String target) throws IOException {
1685 		FileUtils.createSymLink(path, target);
1686 	}
1687 
1688 	/**
1689 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
1690 	 * of this class may take care to provide a safe implementation for this
1691 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
1692 	 *
1693 	 * @param path
1694 	 *            the file to be created
1695 	 * @return <code>true</code> if the file was created, <code>false</code> if
1696 	 *         the file already existed
1697 	 * @throws java.io.IOException
1698 	 * @deprecated use {@link #createNewFileAtomic(File)} instead
1699 	 * @since 4.5
1700 	 */
1701 	@Deprecated
1702 	public boolean createNewFile(File path) throws IOException {
1703 		return path.createNewFile();
1704 	}
1705 
1706 	/**
1707 	 * A token representing a file created by
1708 	 * {@link #createNewFileAtomic(File)}. The token must be retained until the
1709 	 * file has been deleted in order to guarantee that the unique file was
1710 	 * created atomically. As soon as the file is no longer needed the lock
1711 	 * token must be closed.
1712 	 *
1713 	 * @since 4.7
1714 	 */
1715 	public static class LockToken implements Closeable {
1716 		private boolean isCreated;
1717 
1718 		private Optional<Path> link;
1719 
1720 		LockToken(boolean isCreated, Optional<Path> link) {
1721 			this.isCreated = isCreated;
1722 			this.link = link;
1723 		}
1724 
1725 		/**
1726 		 * @return {@code true} if the file was created successfully
1727 		 */
1728 		public boolean isCreated() {
1729 			return isCreated;
1730 		}
1731 
1732 		@Override
1733 		public void close() {
1734 			if (!link.isPresent()) {
1735 				return;
1736 			}
1737 			Path p = link.get();
1738 			if (!Files.exists(p)) {
1739 				return;
1740 			}
1741 			try {
1742 				Files.delete(p);
1743 			} catch (IOException e) {
1744 				LOG.error(MessageFormat
1745 						.format(JGitText.get().closeLockTokenFailed, this), e);
1746 			}
1747 		}
1748 
1749 		@Override
1750 		public String toString() {
1751 			return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
1752 					", link=" //$NON-NLS-1$
1753 					+ (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
1754 							: "<null>]"); //$NON-NLS-1$
1755 		}
1756 	}
1757 
1758 	/**
1759 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
1760 	 * of this class may take care to provide a safe implementation for this
1761 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
1762 	 *
1763 	 * @param path
1764 	 *            the file to be created
1765 	 * @return LockToken this token must be closed after the created file was
1766 	 *         deleted
1767 	 * @throws IOException
1768 	 * @since 4.7
1769 	 */
1770 	public LockToken createNewFileAtomic(File path) throws IOException {
1771 		return new LockToken(path.createNewFile(), Optional.empty());
1772 	}
1773 
1774 	/**
1775 	 * See
1776 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
1777 	 *
1778 	 * @param base
1779 	 *            The path against which <code>other</code> should be
1780 	 *            relativized.
1781 	 * @param other
1782 	 *            The path that will be made relative to <code>base</code>.
1783 	 * @return A relative path that, when resolved against <code>base</code>,
1784 	 *         will yield the original <code>other</code>.
1785 	 * @see FileUtils#relativizePath(String, String, String, boolean)
1786 	 * @since 3.7
1787 	 */
1788 	public String relativize(String base, String other) {
1789 		return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
1790 	}
1791 
1792 	/**
1793 	 * Enumerates children of a directory.
1794 	 *
1795 	 * @param directory
1796 	 *            to get the children of
1797 	 * @param fileModeStrategy
1798 	 *            to use to calculate the git mode of a child
1799 	 * @return an array of entries for the children
1800 	 *
1801 	 * @since 5.0
1802 	 */
1803 	public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1804 		final File[] all = directory.listFiles();
1805 		if (all == null) {
1806 			return NO_ENTRIES;
1807 		}
1808 		final Entry[] result = new Entry[all.length];
1809 		for (int i = 0; i < result.length; i++) {
1810 			result[i] = new FileEntry(all[i], this, fileModeStrategy);
1811 		}
1812 		return result;
1813 	}
1814 
1815 	/**
1816 	 * Checks whether the given hook is defined for the given repository, then
1817 	 * runs it with the given arguments.
1818 	 * <p>
1819 	 * The hook's standard output and error streams will be redirected to
1820 	 * <code>System.out</code> and <code>System.err</code> respectively. The
1821 	 * hook will have no stdin.
1822 	 * </p>
1823 	 *
1824 	 * @param repository
1825 	 *            The repository for which a hook should be run.
1826 	 * @param hookName
1827 	 *            The name of the hook to be executed.
1828 	 * @param args
1829 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1830 	 *            but can be an empty array.
1831 	 * @return The ProcessResult describing this hook's execution.
1832 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1833 	 *             if we fail to run the hook somehow. Causes may include an
1834 	 *             interrupted process or I/O errors.
1835 	 * @since 4.0
1836 	 */
1837 	public ProcessResult runHookIfPresent(Repository repository,
1838 			final String hookName,
1839 			String[] args) throws JGitInternalException {
1840 		return runHookIfPresent(repository, hookName, args, System.out, System.err,
1841 				null);
1842 	}
1843 
1844 	/**
1845 	 * Checks whether the given hook is defined for the given repository, then
1846 	 * runs it with the given arguments.
1847 	 *
1848 	 * @param repository
1849 	 *            The repository for which a hook should be run.
1850 	 * @param hookName
1851 	 *            The name of the hook to be executed.
1852 	 * @param args
1853 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1854 	 *            but can be an empty array.
1855 	 * @param outRedirect
1856 	 *            A print stream on which to redirect the hook's stdout. Can be
1857 	 *            <code>null</code>, in which case the hook's standard output
1858 	 *            will be lost.
1859 	 * @param errRedirect
1860 	 *            A print stream on which to redirect the hook's stderr. Can be
1861 	 *            <code>null</code>, in which case the hook's standard error
1862 	 *            will be lost.
1863 	 * @param stdinArgs
1864 	 *            A string to pass on to the standard input of the hook. May be
1865 	 *            <code>null</code>.
1866 	 * @return The ProcessResult describing this hook's execution.
1867 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1868 	 *             if we fail to run the hook somehow. Causes may include an
1869 	 *             interrupted process or I/O errors.
1870 	 * @since 4.0
1871 	 */
1872 	public ProcessResult runHookIfPresent(Repository repository,
1873 			final String hookName,
1874 			String[] args, PrintStream outRedirect, PrintStream errRedirect,
1875 			String stdinArgs) throws JGitInternalException {
1876 		return new ProcessResult(Status.NOT_SUPPORTED);
1877 	}
1878 
1879 	/**
1880 	 * See
1881 	 * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
1882 	 * . Should only be called by FS supporting shell scripts execution.
1883 	 *
1884 	 * @param repository
1885 	 *            The repository for which a hook should be run.
1886 	 * @param hookName
1887 	 *            The name of the hook to be executed.
1888 	 * @param args
1889 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1890 	 *            but can be an empty array.
1891 	 * @param outRedirect
1892 	 *            A print stream on which to redirect the hook's stdout. Can be
1893 	 *            <code>null</code>, in which case the hook's standard output
1894 	 *            will be lost.
1895 	 * @param errRedirect
1896 	 *            A print stream on which to redirect the hook's stderr. Can be
1897 	 *            <code>null</code>, in which case the hook's standard error
1898 	 *            will be lost.
1899 	 * @param stdinArgs
1900 	 *            A string to pass on to the standard input of the hook. May be
1901 	 *            <code>null</code>.
1902 	 * @return The ProcessResult describing this hook's execution.
1903 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1904 	 *             if we fail to run the hook somehow. Causes may include an
1905 	 *             interrupted process or I/O errors.
1906 	 * @since 4.0
1907 	 */
1908 	protected ProcessResult internalRunHookIfPresent(Repository repository,
1909 			final String hookName, String[] args, PrintStream outRedirect,
1910 			PrintStream errRedirect, String stdinArgs)
1911 			throws JGitInternalException {
1912 		File hookFile = findHook(repository, hookName);
1913 		if (hookFile == null || hookName == null) {
1914 			return new ProcessResult(Status.NOT_PRESENT);
1915 		}
1916 
1917 		File runDirectory = getRunDirectory(repository, hookName);
1918 		if (runDirectory == null) {
1919 			return new ProcessResult(Status.NOT_PRESENT);
1920 		}
1921 		String cmd = hookFile.getAbsolutePath();
1922 		ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
1923 		hookProcess.directory(runDirectory.getAbsoluteFile());
1924 		Map<String, String> environment = hookProcess.environment();
1925 		environment.put(Constants.GIT_DIR_KEY,
1926 				repository.getDirectory().getAbsolutePath());
1927 		if (!repository.isBare()) {
1928 			environment.put(Constants.GIT_WORK_TREE_KEY,
1929 					repository.getWorkTree().getAbsolutePath());
1930 		}
1931 		try {
1932 			return new ProcessResult(runProcess(hookProcess, outRedirect,
1933 					errRedirect, stdinArgs), Status.OK);
1934 		} catch (IOException e) {
1935 			throw new JGitInternalException(MessageFormat.format(
1936 					JGitText.get().exceptionCaughtDuringExecutionOfHook,
1937 					hookName), e);
1938 		} catch (InterruptedException e) {
1939 			throw new JGitInternalException(MessageFormat.format(
1940 					JGitText.get().exceptionHookExecutionInterrupted,
1941 							hookName), e);
1942 		}
1943 	}
1944 
1945 	/**
1946 	 * Quote a string (such as a file system path obtained from a Java
1947 	 * {@link File} or {@link Path} object) such that it can be passed as first
1948 	 * argument to {@link #runInShell(String, String[])}.
1949 	 * <p>
1950 	 * This default implementation returns the string unchanged.
1951 	 * </p>
1952 	 *
1953 	 * @param cmd
1954 	 *            the String to quote
1955 	 * @return the quoted string
1956 	 */
1957 	String shellQuote(String cmd) {
1958 		return cmd;
1959 	}
1960 
1961 	/**
1962 	 * Tries to find a hook matching the given one in the given repository.
1963 	 *
1964 	 * @param repository
1965 	 *            The repository within which to find a hook.
1966 	 * @param hookName
1967 	 *            The name of the hook we're trying to find.
1968 	 * @return The {@link java.io.File} containing this particular hook if it
1969 	 *         exists in the given repository, <code>null</code> otherwise.
1970 	 * @since 4.0
1971 	 */
1972 	public File findHook(Repository repository, String hookName) {
1973 		if (hookName == null) {
1974 			return null;
1975 		}
1976 		File hookDir = getHooksDirectory(repository);
1977 		if (hookDir == null) {
1978 			return null;
1979 		}
1980 		File hookFile = new File(hookDir, hookName);
1981 		if (hookFile.isAbsolute()) {
1982 			if (!hookFile.exists() || (FS.DETECTED.supportsExecute()
1983 					&& !FS.DETECTED.canExecute(hookFile))) {
1984 				return null;
1985 			}
1986 		} else {
1987 			try {
1988 				File runDirectory = getRunDirectory(repository, hookName);
1989 				if (runDirectory == null) {
1990 					return null;
1991 				}
1992 				Path hookPath = runDirectory.getAbsoluteFile().toPath()
1993 						.resolve(hookFile.toPath());
1994 				FS fs = repository.getFS();
1995 				if (fs == null) {
1996 					fs = FS.DETECTED;
1997 				}
1998 				if (!Files.exists(hookPath) || (fs.supportsExecute()
1999 						&& !fs.canExecute(hookPath.toFile()))) {
2000 					return null;
2001 				}
2002 				hookFile = hookPath.toFile();
2003 			} catch (InvalidPathException e) {
2004 				LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath,
2005 						hookFile));
2006 				return null;
2007 			}
2008 		}
2009 		return hookFile;
2010 	}
2011 
2012 	private File getRunDirectory(Repository repository,
2013 			@NonNull String hookName) {
2014 		if (repository.isBare()) {
2015 			return repository.getDirectory();
2016 		}
2017 		switch (hookName) {
2018 		case "pre-receive": //$NON-NLS-1$
2019 		case "update": //$NON-NLS-1$
2020 		case "post-receive": //$NON-NLS-1$
2021 		case "post-update": //$NON-NLS-1$
2022 		case "push-to-checkout": //$NON-NLS-1$
2023 			return repository.getDirectory();
2024 		default:
2025 			return repository.getWorkTree();
2026 		}
2027 	}
2028 
2029 	private File getHooksDirectory(Repository repository) {
2030 		Config config = repository.getConfig();
2031 		String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
2032 				null, ConfigConstants.CONFIG_KEY_HOOKS_PATH);
2033 		if (hooksDir != null) {
2034 			return new File(hooksDir);
2035 		}
2036 		File dir = repository.getDirectory();
2037 		return dir == null ? null : new File(dir, Constants.HOOKS);
2038 	}
2039 
2040 	/**
2041 	 * Runs the given process until termination, clearing its stdout and stderr
2042 	 * streams on-the-fly.
2043 	 *
2044 	 * @param processBuilder
2045 	 *            The process builder configured for this process.
2046 	 * @param outRedirect
2047 	 *            A OutputStream on which to redirect the processes stdout. Can
2048 	 *            be <code>null</code>, in which case the processes standard
2049 	 *            output will be lost.
2050 	 * @param errRedirect
2051 	 *            A OutputStream on which to redirect the processes stderr. Can
2052 	 *            be <code>null</code>, in which case the processes standard
2053 	 *            error will be lost.
2054 	 * @param stdinArgs
2055 	 *            A string to pass on to the standard input of the hook. Can be
2056 	 *            <code>null</code>.
2057 	 * @return the exit value of this process.
2058 	 * @throws java.io.IOException
2059 	 *             if an I/O error occurs while executing this process.
2060 	 * @throws java.lang.InterruptedException
2061 	 *             if the current thread is interrupted while waiting for the
2062 	 *             process to end.
2063 	 * @since 4.2
2064 	 */
2065 	public int runProcess(ProcessBuilder processBuilder,
2066 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
2067 			throws IOException, InterruptedException {
2068 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
2069 						stdinArgs.getBytes(UTF_8));
2070 		return runProcess(processBuilder, outRedirect, errRedirect, in);
2071 	}
2072 
2073 	/**
2074 	 * Runs the given process until termination, clearing its stdout and stderr
2075 	 * streams on-the-fly.
2076 	 *
2077 	 * @param processBuilder
2078 	 *            The process builder configured for this process.
2079 	 * @param outRedirect
2080 	 *            An OutputStream on which to redirect the processes stdout. Can
2081 	 *            be <code>null</code>, in which case the processes standard
2082 	 *            output will be lost.
2083 	 * @param errRedirect
2084 	 *            An OutputStream on which to redirect the processes stderr. Can
2085 	 *            be <code>null</code>, in which case the processes standard
2086 	 *            error will be lost.
2087 	 * @param inRedirect
2088 	 *            An InputStream from which to redirect the processes stdin. Can
2089 	 *            be <code>null</code>, in which case the process doesn't get
2090 	 *            any data over stdin. It is assumed that the whole InputStream
2091 	 *            will be consumed by the process. The method will close the
2092 	 *            inputstream after all bytes are read.
2093 	 * @return the return code of this process.
2094 	 * @throws java.io.IOException
2095 	 *             if an I/O error occurs while executing this process.
2096 	 * @throws java.lang.InterruptedException
2097 	 *             if the current thread is interrupted while waiting for the
2098 	 *             process to end.
2099 	 * @since 4.2
2100 	 */
2101 	public int runProcess(ProcessBuilder processBuilder,
2102 			OutputStream outRedirect, OutputStream errRedirect,
2103 			InputStream inRedirect) throws IOException,
2104 			InterruptedException {
2105 		final ExecutorService executor = Executors.newFixedThreadPool(2);
2106 		Process process = null;
2107 		// We'll record the first I/O exception that occurs, but keep on trying
2108 		// to dispose of our open streams and file handles
2109 		IOException ioException = null;
2110 		try {
2111 			process = processBuilder.start();
2112 			executor.execute(
2113 					new StreamGobbler(process.getErrorStream(), errRedirect));
2114 			executor.execute(
2115 					new StreamGobbler(process.getInputStream(), outRedirect));
2116 			@SuppressWarnings("resource") // Closed in the finally block
2117 			OutputStream outputStream = process.getOutputStream();
2118 			try {
2119 				if (inRedirect != null) {
2120 					new StreamGobbler(inRedirect, outputStream).copy();
2121 				}
2122 			} finally {
2123 				try {
2124 					outputStream.close();
2125 				} catch (IOException e) {
2126 					// When the process exits before consuming the input, the OutputStream
2127 					// is replaced with the null output stream. This null output stream
2128 					// throws IOException for all write calls. When StreamGobbler fails to
2129 					// flush the buffer because of this, this close call tries to flush it
2130 					// again. This causes another IOException. Since we ignore the
2131 					// IOException in StreamGobbler, we also ignore the exception here.
2132 				}
2133 			}
2134 			return process.waitFor();
2135 		} catch (IOException e) {
2136 			ioException = e;
2137 		} finally {
2138 			shutdownAndAwaitTermination(executor);
2139 			if (process != null) {
2140 				try {
2141 					process.waitFor();
2142 				} catch (InterruptedException e) {
2143 					// Thrown by the outer try.
2144 					// Swallow this one to carry on our cleanup, and clear the
2145 					// interrupted flag (processes throw the exception without
2146 					// clearing the flag).
2147 					Thread.interrupted();
2148 				}
2149 				// A process doesn't clean its own resources even when destroyed
2150 				// Explicitly try and close all three streams, preserving the
2151 				// outer I/O exception if any.
2152 				if (inRedirect != null) {
2153 					inRedirect.close();
2154 				}
2155 				try {
2156 					process.getErrorStream().close();
2157 				} catch (IOException e) {
2158 					ioException = ioException != null ? ioException : e;
2159 				}
2160 				try {
2161 					process.getInputStream().close();
2162 				} catch (IOException e) {
2163 					ioException = ioException != null ? ioException : e;
2164 				}
2165 				try {
2166 					process.getOutputStream().close();
2167 				} catch (IOException e) {
2168 					ioException = ioException != null ? ioException : e;
2169 				}
2170 				process.destroy();
2171 			}
2172 		}
2173 		// We can only be here if the outer try threw an IOException.
2174 		throw ioException;
2175 	}
2176 
2177 	/**
2178 	 * Shuts down an {@link ExecutorService} in two phases, first by calling
2179 	 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
2180 	 * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
2181 	 * necessary, to cancel any lingering tasks. Returns true if the pool has
2182 	 * been properly shutdown, false otherwise.
2183 	 * <p>
2184 	 *
2185 	 * @param pool
2186 	 *            the pool to shutdown
2187 	 * @return <code>true</code> if the pool has been properly shutdown,
2188 	 *         <code>false</code> otherwise.
2189 	 */
2190 	private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
2191 		boolean hasShutdown = true;
2192 		pool.shutdown(); // Disable new tasks from being submitted
2193 		try {
2194 			// Wait a while for existing tasks to terminate
2195 			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
2196 				pool.shutdownNow(); // Cancel currently executing tasks
2197 				// Wait a while for tasks to respond to being canceled
2198 				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
2199 					hasShutdown = false;
2200 			}
2201 		} catch (InterruptedException ie) {
2202 			// (Re-)Cancel if current thread also interrupted
2203 			pool.shutdownNow();
2204 			// Preserve interrupt status
2205 			Thread.currentThread().interrupt();
2206 			hasShutdown = false;
2207 		}
2208 		return hasShutdown;
2209 	}
2210 
2211 	/**
2212 	 * Initialize a ProcessBuilder to run a command using the system shell.
2213 	 *
2214 	 * @param cmd
2215 	 *            command to execute. This string should originate from the
2216 	 *            end-user, and thus is platform specific.
2217 	 * @param args
2218 	 *            arguments to pass to command. These should be protected from
2219 	 *            shell evaluation.
2220 	 * @return a partially completed process builder. Caller should finish
2221 	 *         populating directory, environment, and then start the process.
2222 	 */
2223 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
2224 
2225 	/**
2226 	 * Execute a command defined by a {@link java.lang.ProcessBuilder}.
2227 	 *
2228 	 * @param pb
2229 	 *            The command to be executed
2230 	 * @param in
2231 	 *            The standard input stream passed to the process
2232 	 * @return The result of the executed command
2233 	 * @throws java.lang.InterruptedException
2234 	 * @throws java.io.IOException
2235 	 * @since 4.2
2236 	 */
2237 	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
2238 			throws IOException, InterruptedException {
2239 		try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
2240 				TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
2241 						1024 * 1024)) {
2242 			int rc = runProcess(pb, stdout, stderr, in);
2243 			return new ExecutionResult(stdout, stderr, rc);
2244 		}
2245 	}
2246 
2247 	private static class Holder<V> {
2248 		final V value;
2249 
2250 		Holder(V value) {
2251 			this.value = value;
2252 		}
2253 	}
2254 
2255 	/**
2256 	 * File attributes we typically care for.
2257 	 *
2258 	 * @since 3.3
2259 	 */
2260 	public static class Attributes {
2261 
2262 		/**
2263 		 * @return true if this are the attributes of a directory
2264 		 */
2265 		public boolean isDirectory() {
2266 			return isDirectory;
2267 		}
2268 
2269 		/**
2270 		 * @return true if this are the attributes of an executable file
2271 		 */
2272 		public boolean isExecutable() {
2273 			return isExecutable;
2274 		}
2275 
2276 		/**
2277 		 * @return true if this are the attributes of a symbolic link
2278 		 */
2279 		public boolean isSymbolicLink() {
2280 			return isSymbolicLink;
2281 		}
2282 
2283 		/**
2284 		 * @return true if this are the attributes of a regular file
2285 		 */
2286 		public boolean isRegularFile() {
2287 			return isRegularFile;
2288 		}
2289 
2290 		/**
2291 		 * @return the time when the file was created
2292 		 */
2293 		public long getCreationTime() {
2294 			return creationTime;
2295 		}
2296 
2297 		/**
2298 		 * @return the time (milliseconds since 1970-01-01) when this object was
2299 		 *         last modified
2300 		 * @deprecated use getLastModifiedInstant instead
2301 		 */
2302 		@Deprecated
2303 		public long getLastModifiedTime() {
2304 			return lastModifiedInstant.toEpochMilli();
2305 		}
2306 
2307 		/**
2308 		 * @return the time when this object was last modified
2309 		 * @since 5.1.9
2310 		 */
2311 		public Instant getLastModifiedInstant() {
2312 			return lastModifiedInstant;
2313 		}
2314 
2315 		private final boolean isDirectory;
2316 
2317 		private final boolean isSymbolicLink;
2318 
2319 		private final boolean isRegularFile;
2320 
2321 		private final long creationTime;
2322 
2323 		private final Instant lastModifiedInstant;
2324 
2325 		private final boolean isExecutable;
2326 
2327 		private final File file;
2328 
2329 		private final boolean exists;
2330 
2331 		/**
2332 		 * file length
2333 		 */
2334 		protected long length = -1;
2335 
2336 		final FS fs;
2337 
2338 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
2339 				boolean isExecutable, boolean isSymbolicLink,
2340 				boolean isRegularFile, long creationTime,
2341 				Instant lastModifiedInstant, long length) {
2342 			this.fs = fs;
2343 			this.file = file;
2344 			this.exists = exists;
2345 			this.isDirectory = isDirectory;
2346 			this.isExecutable = isExecutable;
2347 			this.isSymbolicLink = isSymbolicLink;
2348 			this.isRegularFile = isRegularFile;
2349 			this.creationTime = creationTime;
2350 			this.lastModifiedInstant = lastModifiedInstant;
2351 			this.length = length;
2352 		}
2353 
2354 		/**
2355 		 * Constructor when there are issues with reading. All attributes except
2356 		 * given will be set to the default values.
2357 		 *
2358 		 * @param fs
2359 		 * @param path
2360 		 */
2361 		public Attributes(File path, FS fs) {
2362 			this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
2363 		}
2364 
2365 		/**
2366 		 * @return length of this file object
2367 		 */
2368 		public long getLength() {
2369 			if (length == -1)
2370 				return length = file.length();
2371 			return length;
2372 		}
2373 
2374 		/**
2375 		 * @return the filename
2376 		 */
2377 		public String getName() {
2378 			return file.getName();
2379 		}
2380 
2381 		/**
2382 		 * @return the file the attributes apply to
2383 		 */
2384 		public File getFile() {
2385 			return file;
2386 		}
2387 
2388 		boolean exists() {
2389 			return exists;
2390 		}
2391 	}
2392 
2393 	/**
2394 	 * Get the file attributes we care for.
2395 	 *
2396 	 * @param path
2397 	 *            a {@link java.io.File} object.
2398 	 * @return the file attributes we care for.
2399 	 * @since 3.3
2400 	 */
2401 	public Attributes getAttributes(File path) {
2402 		boolean isDirectory = isDirectory(path);
2403 		boolean isFile = !isDirectory && path.isFile();
2404 		assert path.exists() == isDirectory || isFile;
2405 		boolean exists = isDirectory || isFile;
2406 		boolean canExecute = exists && !isDirectory && canExecute(path);
2407 		boolean isSymlink = false;
2408 		Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
2409 		long createTime = 0L;
2410 		return new Attributes(this, path, exists, isDirectory, canExecute,
2411 				isSymlink, isFile, createTime, lastModified, -1);
2412 	}
2413 
2414 	/**
2415 	 * Normalize the unicode path to composed form.
2416 	 *
2417 	 * @param file
2418 	 *            a {@link java.io.File} object.
2419 	 * @return NFC-format File
2420 	 * @since 3.3
2421 	 */
2422 	public File normalize(File file) {
2423 		return file;
2424 	}
2425 
2426 	/**
2427 	 * Normalize the unicode path to composed form.
2428 	 *
2429 	 * @param name
2430 	 *            path name
2431 	 * @return NFC-format string
2432 	 * @since 3.3
2433 	 */
2434 	public String normalize(String name) {
2435 		return name;
2436 	}
2437 
2438 	/**
2439 	 * This runnable will consume an input stream's content into an output
2440 	 * stream as soon as it gets available.
2441 	 * <p>
2442 	 * Typically used to empty processes' standard output and error, preventing
2443 	 * them to choke.
2444 	 * </p>
2445 	 * <p>
2446 	 * <b>Note</b> that a {@link StreamGobbler} will never close either of its
2447 	 * streams.
2448 	 * </p>
2449 	 */
2450 	private static class StreamGobbler implements Runnable {
2451 		private InputStream in;
2452 
2453 		private OutputStream out;
2454 
2455 		public StreamGobbler(InputStream stream, OutputStream output) {
2456 			this.in = stream;
2457 			this.out = output;
2458 		}
2459 
2460 		@Override
2461 		public void run() {
2462 			try {
2463 				copy();
2464 			} catch (IOException e) {
2465 				// Do nothing on read failure; leave streams open.
2466 			}
2467 		}
2468 
2469 		void copy() throws IOException {
2470 			boolean writeFailure = false;
2471 			byte[] buffer = new byte[4096];
2472 			int readBytes;
2473 			while ((readBytes = in.read(buffer)) != -1) {
2474 				// Do not try to write again after a failure, but keep
2475 				// reading as long as possible to prevent the input stream
2476 				// from choking.
2477 				if (!writeFailure && out != null) {
2478 					try {
2479 						out.write(buffer, 0, readBytes);
2480 						out.flush();
2481 					} catch (IOException e) {
2482 						writeFailure = true;
2483 					}
2484 				}
2485 			}
2486 		}
2487 	}
2488 }