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