FS.java

  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. package org.eclipse.jgit.util;

  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static java.time.Instant.EPOCH;

  13. import java.io.BufferedReader;
  14. import java.io.ByteArrayInputStream;
  15. import java.io.Closeable;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.InputStreamReader;
  20. import java.io.OutputStream;
  21. import java.io.OutputStreamWriter;
  22. import java.io.Writer;
  23. import java.nio.charset.Charset;
  24. import java.nio.file.AccessDeniedException;
  25. import java.nio.file.FileStore;
  26. import java.nio.file.Files;
  27. import java.nio.file.InvalidPathException;
  28. import java.nio.file.Path;
  29. import java.nio.file.attribute.BasicFileAttributes;
  30. import java.nio.file.attribute.FileTime;
  31. import java.security.AccessControlException;
  32. import java.security.AccessController;
  33. import java.security.PrivilegedAction;
  34. import java.text.MessageFormat;
  35. import java.time.Duration;
  36. import java.time.Instant;
  37. import java.util.ArrayList;
  38. import java.util.Arrays;
  39. import java.util.HashMap;
  40. import java.util.Map;
  41. import java.util.Objects;
  42. import java.util.Optional;
  43. import java.util.UUID;
  44. import java.util.concurrent.CancellationException;
  45. import java.util.concurrent.CompletableFuture;
  46. import java.util.concurrent.ConcurrentHashMap;
  47. import java.util.concurrent.ExecutionException;
  48. import java.util.concurrent.Executor;
  49. import java.util.concurrent.ExecutorService;
  50. import java.util.concurrent.Executors;
  51. import java.util.concurrent.LinkedBlockingQueue;
  52. import java.util.concurrent.ThreadPoolExecutor;
  53. import java.util.concurrent.TimeUnit;
  54. import java.util.concurrent.TimeoutException;
  55. import java.util.concurrent.atomic.AtomicBoolean;
  56. import java.util.concurrent.atomic.AtomicInteger;
  57. import java.util.concurrent.atomic.AtomicReference;
  58. import java.util.concurrent.locks.Lock;
  59. import java.util.concurrent.locks.ReentrantLock;

  60. import org.eclipse.jgit.annotations.NonNull;
  61. import org.eclipse.jgit.annotations.Nullable;
  62. import org.eclipse.jgit.api.errors.JGitInternalException;
  63. import org.eclipse.jgit.errors.CommandFailedException;
  64. import org.eclipse.jgit.errors.ConfigInvalidException;
  65. import org.eclipse.jgit.errors.LockFailedException;
  66. import org.eclipse.jgit.internal.JGitText;
  67. import org.eclipse.jgit.internal.storage.file.FileSnapshot;
  68. import org.eclipse.jgit.lib.Config;
  69. import org.eclipse.jgit.lib.ConfigConstants;
  70. import org.eclipse.jgit.lib.Constants;
  71. import org.eclipse.jgit.lib.Repository;
  72. import org.eclipse.jgit.lib.StoredConfig;
  73. import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
  74. import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
  75. import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
  76. import org.eclipse.jgit.util.ProcessResult.Status;
  77. import org.slf4j.Logger;
  78. import org.slf4j.LoggerFactory;

  79. /**
  80.  * Abstraction to support various file system operations not in Java.
  81.  */
  82. public abstract class FS {
  83.     private static final Logger LOG = LoggerFactory.getLogger(FS.class);

  84.     /**
  85.      * An empty array of entries, suitable as a return value for
  86.      * {@link #list(File, FileModeStrategy)}.
  87.      *
  88.      * @since 5.0
  89.      */
  90.     protected static final Entry[] NO_ENTRIES = {};

  91.     private volatile Boolean supportSymlinks;

  92.     /**
  93.      * This class creates FS instances. It will be overridden by a Java7 variant
  94.      * if such can be detected in {@link #detect(Boolean)}.
  95.      *
  96.      * @since 3.0
  97.      */
  98.     public static class FSFactory {
  99.         /**
  100.          * Constructor
  101.          */
  102.         protected FSFactory() {
  103.             // empty
  104.         }

  105.         /**
  106.          * Detect the file system
  107.          *
  108.          * @param cygwinUsed
  109.          * @return FS instance
  110.          */
  111.         public FS detect(Boolean cygwinUsed) {
  112.             if (SystemReader.getInstance().isWindows()) {
  113.                 if (cygwinUsed == null) {
  114.                     cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  115.                 }
  116.                 if (cygwinUsed.booleanValue()) {
  117.                     return new FS_Win32_Cygwin();
  118.                 }
  119.                 return new FS_Win32();
  120.             }
  121.             return new FS_POSIX();
  122.         }
  123.     }

  124.     /**
  125.      * Result of an executed process. The caller is responsible to close the
  126.      * contained {@link TemporaryBuffer}s
  127.      *
  128.      * @since 4.2
  129.      */
  130.     public static class ExecutionResult {
  131.         private TemporaryBuffer stdout;

  132.         private TemporaryBuffer stderr;

  133.         private int rc;

  134.         /**
  135.          * @param stdout
  136.          * @param stderr
  137.          * @param rc
  138.          */
  139.         public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
  140.                 int rc) {
  141.             this.stdout = stdout;
  142.             this.stderr = stderr;
  143.             this.rc = rc;
  144.         }

  145.         /**
  146.          * @return buffered standard output stream
  147.          */
  148.         public TemporaryBuffer getStdout() {
  149.             return stdout;
  150.         }

  151.         /**
  152.          * @return buffered standard error stream
  153.          */
  154.         public TemporaryBuffer getStderr() {
  155.             return stderr;
  156.         }

  157.         /**
  158.          * @return the return code of the process
  159.          */
  160.         public int getRc() {
  161.             return rc;
  162.         }
  163.     }

  164.     /**
  165.      * Attributes of FileStores on this system
  166.      *
  167.      * @since 5.1.9
  168.      */
  169.     public static final class FileStoreAttributes {

  170.         /**
  171.          * Marker to detect undefined values when reading from the config file.
  172.          */
  173.         private static final Duration UNDEFINED_DURATION = Duration
  174.                 .ofNanos(Long.MAX_VALUE);

  175.         /**
  176.          * Fallback filesystem timestamp resolution. The worst case timestamp
  177.          * resolution on FAT filesystems is 2 seconds.
  178.          * <p>
  179.          * Must be at least 1 second.
  180.          * </p>
  181.          */
  182.         public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
  183.                 .ofMillis(2000);

  184.         /**
  185.          * Fallback FileStore attributes used when we can't measure the
  186.          * filesystem timestamp resolution. The last modified time granularity
  187.          * of FAT filesystems is 2 seconds.
  188.          */
  189.         public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
  190.                 FALLBACK_TIMESTAMP_RESOLUTION);

  191.         private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS
  192.                 .toNanos(1);

  193.         private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS
  194.                 .toNanos(1);

  195.         private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1);

  196.         /**
  197.          * Minimum file system timestamp resolution granularity to check, in
  198.          * nanoseconds. Should be a positive power of ten smaller than
  199.          * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e.,
  200.          * minimum value is 1 nanosecond.
  201.          * <p>
  202.          * Currently set to 1 microsecond, but could also be lower still.
  203.          * </p>
  204.          */
  205.         private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND;

  206.         private static final String JAVA_VERSION_PREFIX = System
  207.                 .getProperty("java.vendor") + '|' //$NON-NLS-1$
  208.                 + System.getProperty("java.version") + '|'; //$NON-NLS-1$

  209.         private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
  210.                 .ofMillis(10);

  211.         private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();

  212.         private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
  213.                 100, 0.2f);

  214.         private static final AtomicBoolean background = new AtomicBoolean();

  215.         private static final Map<FileStore, Lock> locks = new ConcurrentHashMap<>();

  216.         private static final AtomicInteger threadNumber = new AtomicInteger(1);

  217.         /**
  218.          * Don't use the default thread factory of the ForkJoinPool for the
  219.          * CompletableFuture; it runs without any privileges, which causes
  220.          * trouble if a SecurityManager is present.
  221.          * <p>
  222.          * Instead use normal daemon threads. They'll belong to the
  223.          * SecurityManager's thread group, or use the one of the calling thread,
  224.          * as appropriate.
  225.          * </p>
  226.          *
  227.          * @see java.util.concurrent.Executors#newCachedThreadPool()
  228.          */
  229.         private static final Executor FUTURE_RUNNER = new ThreadPoolExecutor(0,
  230.                 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
  231.                 runnable -> {
  232.                     Thread t = new Thread(runnable,
  233.                             "JGit-FileStoreAttributeReader-" //$NON-NLS-1$
  234.                             + threadNumber.getAndIncrement());
  235.                     // Make sure these threads don't prevent application/JVM
  236.                     // shutdown.
  237.                     t.setDaemon(true);
  238.                     return t;
  239.                 });

  240.         /**
  241.          * Use a separate executor with at most one thread to synchronize
  242.          * writing to the config. We write asynchronously since the config
  243.          * itself might be on a different file system, which might otherwise
  244.          * lead to locking problems.
  245.          * <p>
  246.          * Writing the config must not use a daemon thread, otherwise we may
  247.          * leave an inconsistent state on disk when the JVM shuts down. Use a
  248.          * small keep-alive time to avoid delays on shut-down.
  249.          * </p>
  250.          */
  251.         private static final Executor SAVE_RUNNER = new ThreadPoolExecutor(0, 1,
  252.                 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
  253.                 runnable -> {
  254.                     Thread t = new Thread(runnable,
  255.                             "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$
  256.                             + threadNumber.getAndIncrement());
  257.                     // Make sure these threads do finish
  258.                     t.setDaemon(false);
  259.                     return t;
  260.                 });

  261.         /**
  262.          * Whether FileStore attributes should be determined asynchronously
  263.          *
  264.          * @param async
  265.          *            whether FileStore attributes should be determined
  266.          *            asynchronously. If false access to cached attributes may
  267.          *            block for some seconds for the first call per FileStore
  268.          * @since 5.6.2
  269.          */
  270.         public static void setBackground(boolean async) {
  271.             background.set(async);
  272.         }

  273.         /**
  274.          * Configures size and purge factor of the path-based cache for file
  275.          * system attributes. Caching of file system attributes avoids recurring
  276.          * lookup of @{code FileStore} of files which may be expensive on some
  277.          * platforms.
  278.          *
  279.          * @param maxSize
  280.          *            maximum size of the cache, default is 100
  281.          * @param purgeFactor
  282.          *            when the size of the map reaches maxSize the oldest
  283.          *            entries will be purged to free up some space for new
  284.          *            entries, {@code purgeFactor} is the fraction of
  285.          *            {@code maxSize} to purge when this happens
  286.          * @since 5.1.9
  287.          */
  288.         public static void configureAttributesPathCache(int maxSize,
  289.                 float purgeFactor) {
  290.             FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
  291.         }

  292.         /**
  293.          * Get the FileStoreAttributes for the given FileStore
  294.          *
  295.          * @param path
  296.          *            file residing in the FileStore to get attributes for
  297.          * @return FileStoreAttributes for the given path.
  298.          */
  299.         public static FileStoreAttributes get(Path path) {
  300.             try {
  301.                 path = path.toAbsolutePath();
  302.                 Path dir = Files.isDirectory(path) ? path : path.getParent();
  303.                 if (dir == null) {
  304.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  305.                 }
  306.                 FileStoreAttributes cached = attrCacheByPath.get(dir);
  307.                 if (cached != null) {
  308.                     return cached;
  309.                 }
  310.                 FileStoreAttributes attrs = getFileStoreAttributes(dir);
  311.                 if (attrs == null) {
  312.                     // Don't cache, result might be late
  313.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  314.                 }
  315.                 attrCacheByPath.put(dir, attrs);
  316.                 return attrs;
  317.             } catch (SecurityException e) {
  318.                 return FALLBACK_FILESTORE_ATTRIBUTES;
  319.             }
  320.         }

  321.         private static FileStoreAttributes getFileStoreAttributes(Path dir) {
  322.             FileStore s;
  323.             try {
  324.                 if (Files.exists(dir)) {
  325.                     s = Files.getFileStore(dir);
  326.                     FileStoreAttributes c = attributeCache.get(s);
  327.                     if (c != null) {
  328.                         return c;
  329.                     }
  330.                     if (!Files.isWritable(dir)) {
  331.                         // cannot measure resolution in a read-only directory
  332.                         LOG.debug(
  333.                                 "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
  334.                                 Thread.currentThread(), dir);
  335.                         return FALLBACK_FILESTORE_ATTRIBUTES;
  336.                     }
  337.                 } else {
  338.                     // cannot determine FileStore of an unborn directory
  339.                     LOG.debug(
  340.                             "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
  341.                             Thread.currentThread(), dir);
  342.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  343.                 }

  344.                 CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
  345.                         .supplyAsync(() -> {
  346.                             Lock lock = locks.computeIfAbsent(s,
  347.                                     l -> new ReentrantLock());
  348.                             if (!lock.tryLock()) {
  349.                                 LOG.debug(
  350.                                         "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
  351.                                         Thread.currentThread(), dir);
  352.                                 return Optional.empty();
  353.                             }
  354.                             Optional<FileStoreAttributes> attributes = Optional
  355.                                     .empty();
  356.                             try {
  357.                                 // Some earlier future might have set the value
  358.                                 // and removed itself since we checked for the
  359.                                 // value above. Hence check cache again.
  360.                                 FileStoreAttributes c = attributeCache.get(s);
  361.                                 if (c != null) {
  362.                                     return Optional.of(c);
  363.                                 }
  364.                                 attributes = readFromConfig(s);
  365.                                 if (attributes.isPresent()) {
  366.                                     attributeCache.put(s, attributes.get());
  367.                                     return attributes;
  368.                                 }

  369.                                 Optional<Duration> resolution = measureFsTimestampResolution(
  370.                                         s, dir);
  371.                                 if (resolution.isPresent()) {
  372.                                     c = new FileStoreAttributes(
  373.                                             resolution.get());
  374.                                     attributeCache.put(s, c);
  375.                                     // for high timestamp resolution measure
  376.                                     // minimal racy interval
  377.                                     if (c.fsTimestampResolution
  378.                                             .toNanos() < 100_000_000L) {
  379.                                         c.minimalRacyInterval = measureMinimalRacyInterval(
  380.                                                 dir);
  381.                                     }
  382.                                     if (LOG.isDebugEnabled()) {
  383.                                         LOG.debug(c.toString());
  384.                                     }
  385.                                     FileStoreAttributes newAttrs = c;
  386.                                     SAVE_RUNNER.execute(
  387.                                             () -> saveToConfig(s, newAttrs));
  388.                                 }
  389.                                 attributes = Optional.of(c);
  390.                             } finally {
  391.                                 lock.unlock();
  392.                                 locks.remove(s);
  393.                             }
  394.                             return attributes;
  395.                         }, FUTURE_RUNNER);
  396.                 f = f.exceptionally(e -> {
  397.                     LOG.error(e.getLocalizedMessage(), e);
  398.                     return Optional.empty();
  399.                 });
  400.                 // even if measuring in background wait a little - if the result
  401.                 // arrives, it's better than returning the large fallback
  402.                 boolean runInBackground = background.get();
  403.                 Optional<FileStoreAttributes> d = runInBackground ? f.get(
  404.                         100, TimeUnit.MILLISECONDS) : f.get();
  405.                 if (d.isPresent()) {
  406.                     return d.get();
  407.                 } else if (runInBackground) {
  408.                     // return null until measurement is finished
  409.                     return null;
  410.                 }
  411.                 // fall through and return fallback
  412.             } catch (IOException | InterruptedException
  413.                     | ExecutionException | CancellationException e) {
  414.                 LOG.error(e.getMessage(), e);
  415.             } catch (TimeoutException | SecurityException e) {
  416.                 // use fallback
  417.             }
  418.             LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$
  419.                     Thread.currentThread(), dir);
  420.             return FALLBACK_FILESTORE_ATTRIBUTES;
  421.         }

  422.         @SuppressWarnings("boxing")
  423.         private static Duration measureMinimalRacyInterval(Path dir) {
  424.             LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
  425.                     Thread.currentThread(), dir);
  426.             int n = 0;
  427.             int failures = 0;
  428.             long racyNanos = 0;
  429.             ArrayList<Long> deltas = new ArrayList<>();
  430.             Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  431.             Instant end = Instant.now().plusSeconds(3);
  432.             try {
  433.                 Files.createFile(probe);
  434.                 do {
  435.                     n++;
  436.                     write(probe, "a"); //$NON-NLS-1$
  437.                     FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
  438.                     read(probe);
  439.                     write(probe, "b"); //$NON-NLS-1$
  440.                     if (!snapshot.isModified(probe.toFile())) {
  441.                         deltas.add(Long.valueOf(snapshot.lastDelta()));
  442.                         racyNanos = snapshot.lastRacyThreshold();
  443.                         failures++;
  444.                     }
  445.                 } while (Instant.now().compareTo(end) < 0);
  446.             } catch (IOException e) {
  447.                 LOG.error(e.getMessage(), e);
  448.                 return FALLBACK_MIN_RACY_INTERVAL;
  449.             } finally {
  450.                 deleteProbe(probe);
  451.             }
  452.             if (failures > 0) {
  453.                 Stats stats = new Stats();
  454.                 for (Long d : deltas) {
  455.                     stats.add(d);
  456.                 }
  457.                 LOG.debug(
  458.                         "delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$
  459.                                 + "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$
  460.                                 + " delta max [ns], delta avg [ns]," //$NON-NLS-1$
  461.                                 + " delta stddev [ns]\n" //$NON-NLS-1$
  462.                                 + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
  463.                         n, failures, racyNanos, stats.min(), stats.max(),
  464.                         stats.avg(), stats.stddev());
  465.                 return Duration
  466.                         .ofNanos(Double.valueOf(stats.max()).longValue());
  467.             }
  468.             // since no failures occurred using the measured filesystem
  469.             // timestamp resolution there is no need for minimal racy interval
  470.             LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$
  471.                     Thread.currentThread());
  472.             return Duration.ZERO;
  473.         }

  474.         private static void write(Path p, String body) throws IOException {
  475.             Path parent = p.getParent();
  476.             if (parent != null) {
  477.                 FileUtils.mkdirs(parent.toFile(), true);
  478.             }
  479.             try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
  480.                     UTF_8)) {
  481.                 w.write(body);
  482.             }
  483.         }

  484.         private static String read(Path p) throws IOException {
  485.             final byte[] body = IO.readFully(p.toFile());
  486.             return new String(body, 0, body.length, UTF_8);
  487.         }

  488.         private static Optional<Duration> measureFsTimestampResolution(
  489.             FileStore s, Path dir) {
  490.             if (LOG.isDebugEnabled()) {
  491.                 LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
  492.                         Thread.currentThread(), s, dir);
  493.             }
  494.             Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  495.             try {
  496.                 Files.createFile(probe);
  497.                 Duration fsResolution = getFsResolution(s, dir, probe);
  498.                 Duration clockResolution = measureClockResolution();
  499.                 fsResolution = fsResolution.plus(clockResolution);
  500.                 if (LOG.isDebugEnabled()) {
  501.                     LOG.debug(
  502.                             "{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$
  503.                             Thread.currentThread(), s, dir, fsResolution);
  504.                 }
  505.                 return Optional.of(fsResolution);
  506.             } catch (SecurityException e) {
  507.                 // Log it here; most likely deleteProbe() below will also run
  508.                 // into a SecurityException, and then this one will be lost
  509.                 // without trace.
  510.                 LOG.warn(e.getLocalizedMessage(), e);
  511.             } catch (AccessDeniedException e) {
  512.                 LOG.warn(e.getLocalizedMessage(), e); // see bug 548648
  513.             } catch (IOException e) {
  514.                 LOG.error(e.getLocalizedMessage(), e);
  515.             } finally {
  516.                 deleteProbe(probe);
  517.             }
  518.             return Optional.empty();
  519.         }

  520.         private static Duration getFsResolution(FileStore s, Path dir,
  521.                 Path probe) throws IOException {
  522.             File probeFile = probe.toFile();
  523.             FileTime t1 = Files.getLastModifiedTime(probe);
  524.             Instant t1i = t1.toInstant();
  525.             FileTime t2;
  526.             Duration last = FALLBACK_TIMESTAMP_RESOLUTION;
  527.             long minScale = MINIMUM_RESOLUTION_NANOS;
  528.             long scale = ONE_SECOND;
  529.             long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis());
  530.             long low = 0;
  531.             // Try up-front at microsecond and millisecond
  532.             long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND };
  533.             for (long interval : tries) {
  534.                 if (interval >= ONE_MILLISECOND) {
  535.                     probeFile.setLastModified(
  536.                             t1i.plusNanos(interval).toEpochMilli());
  537.                 } else {
  538.                     Files.setLastModifiedTime(probe,
  539.                             FileTime.from(t1i.plusNanos(interval)));
  540.                 }
  541.                 t2 = Files.getLastModifiedTime(probe);
  542.                 if (t2.compareTo(t1) > 0) {
  543.                     Duration diff = Duration.between(t1i, t2.toInstant());
  544.                     if (!diff.isZero() && !diff.isNegative()
  545.                             && diff.compareTo(last) < 0) {
  546.                         scale = interval;
  547.                         high = 1;
  548.                         last = diff;
  549.                         break;
  550.                     }
  551.                 } else {
  552.                     // Makes no sense going below
  553.                     minScale = Math.max(minScale, interval);
  554.                 }
  555.             }
  556.             // Binary search loop
  557.             while (high > low) {
  558.                 long mid = (high + low) / 2;
  559.                 if (mid == 0) {
  560.                     // Smaller than current scale. Adjust scale.
  561.                     long newScale = scale / 10;
  562.                     if (newScale < minScale) {
  563.                         break;
  564.                     }
  565.                     high *= scale / newScale;
  566.                     low *= scale / newScale;
  567.                     scale = newScale;
  568.                     mid = (high + low) / 2;
  569.                 }
  570.                 long delta = mid * scale;
  571.                 if (scale >= ONE_MILLISECOND) {
  572.                     probeFile.setLastModified(
  573.                             t1i.plusNanos(delta).toEpochMilli());
  574.                 } else {
  575.                     Files.setLastModifiedTime(probe,
  576.                             FileTime.from(t1i.plusNanos(delta)));
  577.                 }
  578.                 t2 = Files.getLastModifiedTime(probe);
  579.                 int cmp = t2.compareTo(t1);
  580.                 if (cmp > 0) {
  581.                     high = mid;
  582.                     Duration diff = Duration.between(t1i, t2.toInstant());
  583.                     if (diff.isZero() || diff.isNegative()) {
  584.                         LOG.warn(JGitText.get().logInconsistentFiletimeDiff,
  585.                                 Thread.currentThread(), s, dir, t2, t1, diff,
  586.                                 last);
  587.                         break;
  588.                     } else if (diff.compareTo(last) > 0) {
  589.                         LOG.warn(JGitText.get().logLargerFiletimeDiff,
  590.                                 Thread.currentThread(), s, dir, diff, last);
  591.                         break;
  592.                     }
  593.                     last = diff;
  594.                 } else if (cmp < 0) {
  595.                     LOG.warn(JGitText.get().logSmallerFiletime,
  596.                             Thread.currentThread(), s, dir, t2, t1, last);
  597.                     break;
  598.                 } else {
  599.                     // No discernible difference
  600.                     low = mid + 1;
  601.                 }
  602.             }
  603.             return last;
  604.         }

  605.         private static Duration measureClockResolution() {
  606.             Duration clockResolution = Duration.ZERO;
  607.             for (int i = 0; i < 10; i++) {
  608.                 Instant t1 = Instant.now();
  609.                 Instant t2 = t1;
  610.                 while (t2.compareTo(t1) <= 0) {
  611.                     t2 = Instant.now();
  612.                 }
  613.                 Duration r = Duration.between(t1, t2);
  614.                 if (r.compareTo(clockResolution) > 0) {
  615.                     clockResolution = r;
  616.                 }
  617.             }
  618.             return clockResolution;
  619.         }

  620.         private static void deleteProbe(Path probe) {
  621.             try {
  622.                 FileUtils.delete(probe.toFile(),
  623.                         FileUtils.SKIP_MISSING | FileUtils.RETRY);
  624.             } catch (IOException e) {
  625.                 LOG.error(e.getMessage(), e);
  626.             }
  627.         }

  628.         private static Optional<FileStoreAttributes> readFromConfig(
  629.                 FileStore s) {
  630.             StoredConfig userConfig;
  631.             try {
  632.                 userConfig = SystemReader.getInstance().getUserConfig();
  633.             } catch (IOException | ConfigInvalidException e) {
  634.                 LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
  635.                 return Optional.empty();
  636.             }
  637.             String key = getConfigKey(s);
  638.             Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
  639.                     ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  640.                     ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  641.                     UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  642.             if (UNDEFINED_DURATION.equals(resolution)) {
  643.                 return Optional.empty();
  644.             }
  645.             Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
  646.                     ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  647.                     ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  648.                     UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  649.             FileStoreAttributes c = new FileStoreAttributes(resolution);
  650.             if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
  651.                 c.minimalRacyInterval = minRacyThreshold;
  652.             }
  653.             return Optional.of(c);
  654.         }

  655.         private static void saveToConfig(FileStore s,
  656.                 FileStoreAttributes c) {
  657.             StoredConfig jgitConfig;
  658.             try {
  659.                 jgitConfig = SystemReader.getInstance().getJGitConfig();
  660.             } catch (IOException | ConfigInvalidException e) {
  661.                 LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
  662.                 return;
  663.             }
  664.             long resolution = c.getFsTimestampResolution().toNanos();
  665.             TimeUnit resolutionUnit = getUnit(resolution);
  666.             long resolutionValue = resolutionUnit.convert(resolution,
  667.                     TimeUnit.NANOSECONDS);

  668.             long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
  669.             TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
  670.             long minRacyThresholdValue = minRacyThresholdUnit
  671.                     .convert(minRacyThreshold, TimeUnit.NANOSECONDS);

  672.             final int max_retries = 5;
  673.             int retries = 0;
  674.             boolean succeeded = false;
  675.             String key = getConfigKey(s);
  676.             while (!succeeded && retries < max_retries) {
  677.                 try {
  678.                     jgitConfig.setString(
  679.                             ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  680.                             ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  681.                             String.format("%d %s", //$NON-NLS-1$
  682.                                     Long.valueOf(resolutionValue),
  683.                                     resolutionUnit.name().toLowerCase()));
  684.                     jgitConfig.setString(
  685.                             ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  686.                             ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  687.                             String.format("%d %s", //$NON-NLS-1$
  688.                                     Long.valueOf(minRacyThresholdValue),
  689.                                     minRacyThresholdUnit.name().toLowerCase()));
  690.                     jgitConfig.save();
  691.                     succeeded = true;
  692.                 } catch (LockFailedException e) {
  693.                     // race with another thread, wait a bit and try again
  694.                     try {
  695.                         retries++;
  696.                         if (retries < max_retries) {
  697.                             Thread.sleep(100);
  698.                             LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$
  699.                                     jgitConfig, Integer.valueOf(retries),
  700.                                     Integer.valueOf(max_retries));
  701.                         } else {
  702.                             LOG.warn(MessageFormat.format(
  703.                                     JGitText.get().lockFailedRetry, jgitConfig,
  704.                                     Integer.valueOf(retries)));
  705.                         }
  706.                     } catch (InterruptedException e1) {
  707.                         Thread.currentThread().interrupt();
  708.                         break;
  709.                     }
  710.                 } catch (IOException e) {
  711.                     LOG.error(MessageFormat.format(
  712.                             JGitText.get().cannotSaveConfig, jgitConfig), e);
  713.                     break;
  714.                 }
  715.             }
  716.         }

  717.         private static String getConfigKey(FileStore s) {
  718.             final String storeKey;
  719.             if (SystemReader.getInstance().isWindows()) {
  720.                 Object attribute = null;
  721.                 try {
  722.                     attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
  723.                 } catch (IOException ignored) {
  724.                     // ignore
  725.                 }
  726.                 if (attribute instanceof Integer) {
  727.                     storeKey = attribute.toString();
  728.                 } else {
  729.                     storeKey = s.name();
  730.                 }
  731.             } else {
  732.                 storeKey = s.name();
  733.             }
  734.             return JAVA_VERSION_PREFIX + storeKey;
  735.         }

  736.         private static TimeUnit getUnit(long nanos) {
  737.             TimeUnit unit;
  738.             if (nanos < 200_000L) {
  739.                 unit = TimeUnit.NANOSECONDS;
  740.             } else if (nanos < 200_000_000L) {
  741.                 unit = TimeUnit.MICROSECONDS;
  742.             } else {
  743.                 unit = TimeUnit.MILLISECONDS;
  744.             }
  745.             return unit;
  746.         }

  747.         private final @NonNull Duration fsTimestampResolution;

  748.         private Duration minimalRacyInterval;

  749.         /**
  750.          * @return the measured minimal interval after a file has been modified
  751.          *         in which we cannot rely on lastModified to detect
  752.          *         modifications
  753.          */
  754.         public Duration getMinimalRacyInterval() {
  755.             return minimalRacyInterval;
  756.         }

  757.         /**
  758.          * @return the measured filesystem timestamp resolution
  759.          */
  760.         @NonNull
  761.         public Duration getFsTimestampResolution() {
  762.             return fsTimestampResolution;
  763.         }

  764.         /**
  765.          * Construct a FileStoreAttributeCache entry for the given filesystem
  766.          * timestamp resolution
  767.          *
  768.          * @param fsTimestampResolution
  769.          */
  770.         public FileStoreAttributes(
  771.                 @NonNull Duration fsTimestampResolution) {
  772.             this.fsTimestampResolution = fsTimestampResolution;
  773.             this.minimalRacyInterval = Duration.ZERO;
  774.         }

  775.         @SuppressWarnings({ "nls", "boxing" })
  776.         @Override
  777.         public String toString() {
  778.             return String.format(
  779.                     "FileStoreAttributes[fsTimestampResolution=%,d µs, "
  780.                             + "minimalRacyInterval=%,d µs]",
  781.                     fsTimestampResolution.toNanos() / 1000,
  782.                     minimalRacyInterval.toNanos() / 1000);
  783.         }

  784.     }

  785.     /** The auto-detected implementation selected for this operating system and JRE. */
  786.     public static final FS DETECTED = detect();

  787.     private static volatile FSFactory factory;

  788.     /**
  789.      * Auto-detect the appropriate file system abstraction.
  790.      *
  791.      * @return detected file system abstraction
  792.      */
  793.     public static FS detect() {
  794.         return detect(null);
  795.     }

  796.     /**
  797.      * Whether FileStore attributes should be determined asynchronously
  798.      *
  799.      * @param asynch
  800.      *            whether FileStore attributes should be determined
  801.      *            asynchronously. If false access to cached attributes may block
  802.      *            for some seconds for the first call per FileStore
  803.      * @since 5.1.9
  804.      * @deprecated Use {@link FileStoreAttributes#setBackground} instead
  805.      */
  806.     @Deprecated
  807.     public static void setAsyncFileStoreAttributes(boolean asynch) {
  808.         FileStoreAttributes.setBackground(asynch);
  809.     }

  810.     /**
  811.      * Auto-detect the appropriate file system abstraction, taking into account
  812.      * the presence of a Cygwin installation on the system. Using jgit in
  813.      * combination with Cygwin requires a more elaborate (and possibly slower)
  814.      * resolution of file system paths.
  815.      *
  816.      * @param cygwinUsed
  817.      *            <ul>
  818.      *            <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  819.      *            combination with jgit</li>
  820.      *            <li><code>Boolean.FALSE</code> to assume that Cygwin is
  821.      *            <b>not</b> used with jgit</li>
  822.      *            <li><code>null</code> to auto-detect whether a Cygwin
  823.      *            installation is present on the system and in this case assume
  824.      *            that Cygwin is used</li>
  825.      *            </ul>
  826.      *
  827.      *            Note: this parameter is only relevant on Windows.
  828.      * @return detected file system abstraction
  829.      */
  830.     public static FS detect(Boolean cygwinUsed) {
  831.         if (factory == null) {
  832.             factory = new FS.FSFactory();
  833.         }
  834.         return factory.detect(cygwinUsed);
  835.     }

  836.     /**
  837.      * Get cached FileStore attributes, if not yet available measure them using
  838.      * a probe file under the given directory.
  839.      *
  840.      * @param dir
  841.      *            the directory under which the probe file will be created to
  842.      *            measure the timer resolution.
  843.      * @return measured filesystem timestamp resolution
  844.      * @since 5.1.9
  845.      */
  846.     public static FileStoreAttributes getFileStoreAttributes(
  847.             @NonNull Path dir) {
  848.         return FileStoreAttributes.get(dir);
  849.     }

  850.     private volatile Holder<File> userHome;

  851.     private volatile Holder<File> gitSystemConfig;

  852.     /**
  853.      * Constructs a file system abstraction.
  854.      */
  855.     protected FS() {
  856.         // Do nothing by default.
  857.     }

  858.     /**
  859.      * Initialize this FS using another's current settings.
  860.      *
  861.      * @param src
  862.      *            the source FS to copy from.
  863.      */
  864.     protected FS(FS src) {
  865.         userHome = src.userHome;
  866.         gitSystemConfig = src.gitSystemConfig;
  867.     }

  868.     /**
  869.      * Create a new instance of the same type of FS.
  870.      *
  871.      * @return a new instance of the same type of FS.
  872.      */
  873.     public abstract FS newInstance();

  874.     /**
  875.      * Does this operating system and JRE support the execute flag on files?
  876.      *
  877.      * @return true if this implementation can provide reasonably accurate
  878.      *         executable bit information; false otherwise.
  879.      */
  880.     public abstract boolean supportsExecute();

  881.     /**
  882.      * Does this file system support atomic file creation via
  883.      * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
  884.      * not guaranteed that when two file system clients run createNewFile() in
  885.      * parallel only one will succeed. In such cases both clients may think they
  886.      * created a new file.
  887.      *
  888.      * @return true if this implementation support atomic creation of new Files
  889.      *         by {@link java.io.File#createNewFile()}
  890.      * @since 4.5
  891.      */
  892.     public boolean supportsAtomicCreateNewFile() {
  893.         return true;
  894.     }

  895.     /**
  896.      * Does this operating system and JRE supports symbolic links. The
  897.      * capability to handle symbolic links is detected at runtime.
  898.      *
  899.      * @return true if symbolic links may be used
  900.      * @since 3.0
  901.      */
  902.     public boolean supportsSymlinks() {
  903.         if (supportSymlinks == null) {
  904.             detectSymlinkSupport();
  905.         }
  906.         return Boolean.TRUE.equals(supportSymlinks);
  907.     }

  908.     private void detectSymlinkSupport() {
  909.         File tempFile = null;
  910.         try {
  911.             tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$
  912.             File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$
  913.             createSymLink(linkName, tempFile.getPath());
  914.             supportSymlinks = Boolean.TRUE;
  915.             linkName.delete();
  916.         } catch (IOException | UnsupportedOperationException | SecurityException
  917.                 | InternalError e) {
  918.             supportSymlinks = Boolean.FALSE;
  919.         } finally {
  920.             if (tempFile != null) {
  921.                 try {
  922.                     FileUtils.delete(tempFile);
  923.                 } catch (IOException e) {
  924.                     LOG.error(JGitText.get().cannotDeleteFile, tempFile);
  925.                 }
  926.             }
  927.         }
  928.     }

  929.     /**
  930.      * Is this file system case sensitive
  931.      *
  932.      * @return true if this implementation is case sensitive
  933.      */
  934.     public abstract boolean isCaseSensitive();

  935.     /**
  936.      * Determine if the file is executable (or not).
  937.      * <p>
  938.      * Not all platforms and JREs support executable flags on files. If the
  939.      * feature is unsupported this method will always return false.
  940.      * <p>
  941.      * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  942.      * this method returns false, rather than the state of the executable flags
  943.      * on the target file.</em>
  944.      *
  945.      * @param f
  946.      *            abstract path to test.
  947.      * @return true if the file is believed to be executable by the user.
  948.      */
  949.     public abstract boolean canExecute(File f);

  950.     /**
  951.      * Set a file to be executable by the user.
  952.      * <p>
  953.      * Not all platforms and JREs support executable flags on files. If the
  954.      * feature is unsupported this method will always return false and no
  955.      * changes will be made to the file specified.
  956.      *
  957.      * @param f
  958.      *            path to modify the executable status of.
  959.      * @param canExec
  960.      *            true to enable execution; false to disable it.
  961.      * @return true if the change succeeded; false otherwise.
  962.      */
  963.     public abstract boolean setExecute(File f, boolean canExec);

  964.     /**
  965.      * Get the last modified time of a file system object. If the OS/JRE support
  966.      * symbolic links, the modification time of the link is returned, rather
  967.      * than that of the link target.
  968.      *
  969.      * @param f
  970.      *            a {@link java.io.File} object.
  971.      * @return last modified time of f
  972.      * @throws java.io.IOException
  973.      * @since 3.0
  974.      * @deprecated use {@link #lastModifiedInstant(Path)} instead
  975.      */
  976.     @Deprecated
  977.     public long lastModified(File f) throws IOException {
  978.         return FileUtils.lastModified(f);
  979.     }

  980.     /**
  981.      * Get the last modified time of a file system object. If the OS/JRE support
  982.      * symbolic links, the modification time of the link is returned, rather
  983.      * than that of the link target.
  984.      *
  985.      * @param p
  986.      *            a {@link Path} object.
  987.      * @return last modified time of p
  988.      * @since 5.1.9
  989.      */
  990.     public Instant lastModifiedInstant(Path p) {
  991.         return FileUtils.lastModifiedInstant(p);
  992.     }

  993.     /**
  994.      * Get the last modified time of a file system object. If the OS/JRE support
  995.      * symbolic links, the modification time of the link is returned, rather
  996.      * than that of the link target.
  997.      *
  998.      * @param f
  999.      *            a {@link File} object.
  1000.      * @return last modified time of p
  1001.      * @since 5.1.9
  1002.      */
  1003.     public Instant lastModifiedInstant(File f) {
  1004.         return FileUtils.lastModifiedInstant(f.toPath());
  1005.     }

  1006.     /**
  1007.      * Set the last modified time of a file system object.
  1008.      * <p>
  1009.      * For symlinks it sets the modified time of the link target.
  1010.      *
  1011.      * @param f
  1012.      *            a {@link java.io.File} object.
  1013.      * @param time
  1014.      *            last modified time
  1015.      * @throws java.io.IOException
  1016.      * @since 3.0
  1017.      * @deprecated use {@link #setLastModified(Path, Instant)} instead
  1018.      */
  1019.     @Deprecated
  1020.     public void setLastModified(File f, long time) throws IOException {
  1021.         FileUtils.setLastModified(f, time);
  1022.     }

  1023.     /**
  1024.      * Set the last modified time of a file system object.
  1025.      * <p>
  1026.      * For symlinks it sets the modified time of the link target.
  1027.      *
  1028.      * @param p
  1029.      *            a {@link Path} object.
  1030.      * @param time
  1031.      *            last modified time
  1032.      * @throws java.io.IOException
  1033.      * @since 5.1.9
  1034.      */
  1035.     public void setLastModified(Path p, Instant time) throws IOException {
  1036.         FileUtils.setLastModified(p, time);
  1037.     }

  1038.     /**
  1039.      * Get the length of a file or link, If the OS/JRE supports symbolic links
  1040.      * it's the length of the link, else the length of the target.
  1041.      *
  1042.      * @param path
  1043.      *            a {@link java.io.File} object.
  1044.      * @return length of a file
  1045.      * @throws java.io.IOException
  1046.      * @since 3.0
  1047.      */
  1048.     public long length(File path) throws IOException {
  1049.         return FileUtils.getLength(path);
  1050.     }

  1051.     /**
  1052.      * Delete a file. Throws an exception if delete fails.
  1053.      *
  1054.      * @param f
  1055.      *            a {@link java.io.File} object.
  1056.      * @throws java.io.IOException
  1057.      *             this may be a Java7 subclass with detailed information
  1058.      * @since 3.3
  1059.      */
  1060.     public void delete(File f) throws IOException {
  1061.         FileUtils.delete(f);
  1062.     }

  1063.     /**
  1064.      * Resolve this file to its actual path name that the JRE can use.
  1065.      * <p>
  1066.      * This method can be relatively expensive. Computing a translation may
  1067.      * require forking an external process per path name translated. Callers
  1068.      * should try to minimize the number of translations necessary by caching
  1069.      * the results.
  1070.      * <p>
  1071.      * Not all platforms and JREs require path name translation. Currently only
  1072.      * Cygwin on Win32 require translation for Cygwin based paths.
  1073.      *
  1074.      * @param dir
  1075.      *            directory relative to which the path name is.
  1076.      * @param name
  1077.      *            path name to translate.
  1078.      * @return the translated path. <code>new File(dir,name)</code> if this
  1079.      *         platform does not require path name translation.
  1080.      */
  1081.     public File resolve(File dir, String name) {
  1082.         final File abspn = new File(name);
  1083.         if (abspn.isAbsolute())
  1084.             return abspn;
  1085.         return new File(dir, name);
  1086.     }

  1087.     /**
  1088.      * Determine the user's home directory (location where preferences are).
  1089.      * <p>
  1090.      * This method can be expensive on the first invocation if path name
  1091.      * translation is required. Subsequent invocations return a cached result.
  1092.      * <p>
  1093.      * Not all platforms and JREs require path name translation. Currently only
  1094.      * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  1095.      *
  1096.      * @return the user's home directory; null if the user does not have one.
  1097.      */
  1098.     public File userHome() {
  1099.         Holder<File> p = userHome;
  1100.         if (p == null) {
  1101.             p = new Holder<>(safeUserHomeImpl());
  1102.             userHome = p;
  1103.         }
  1104.         return p.value;
  1105.     }

  1106.     private File safeUserHomeImpl() {
  1107.         File home;
  1108.         try {
  1109.             home = userHomeImpl();
  1110.             if (home != null) {
  1111.                 home.toPath();
  1112.                 return home;
  1113.             }
  1114.         } catch (RuntimeException e) {
  1115.             LOG.error(JGitText.get().exceptionWhileFindingUserHome, e);
  1116.         }
  1117.         home = defaultUserHomeImpl();
  1118.         if (home != null) {
  1119.             try {
  1120.                 home.toPath();
  1121.                 return home;
  1122.             } catch (InvalidPathException e) {
  1123.                 LOG.error(MessageFormat
  1124.                         .format(JGitText.get().invalidHomeDirectory, home), e);
  1125.             }
  1126.         }
  1127.         return null;
  1128.     }

  1129.     /**
  1130.      * Set the user's home directory location.
  1131.      *
  1132.      * @param path
  1133.      *            the location of the user's preferences; null if there is no
  1134.      *            home directory for the current user.
  1135.      * @return {@code this}.
  1136.      */
  1137.     public FS setUserHome(File path) {
  1138.         userHome = new Holder<>(path);
  1139.         return this;
  1140.     }

  1141.     /**
  1142.      * Does this file system have problems with atomic renames?
  1143.      *
  1144.      * @return true if the caller should retry a failed rename of a lock file.
  1145.      */
  1146.     public abstract boolean retryFailedLockFileCommit();

  1147.     /**
  1148.      * Return all the attributes of a file, without following symbolic links.
  1149.      *
  1150.      * @param file
  1151.      * @return {@link BasicFileAttributes} of the file
  1152.      * @throws IOException in case of any I/O errors accessing the file
  1153.      *
  1154.      * @since 4.5.6
  1155.      */
  1156.     public BasicFileAttributes fileAttributes(File file) throws IOException {
  1157.         return FileUtils.fileAttributes(file);
  1158.     }

  1159.     /**
  1160.      * Determine the user's home directory (location where preferences are).
  1161.      *
  1162.      * @return the user's home directory; null if the user does not have one.
  1163.      */
  1164.     protected File userHomeImpl() {
  1165.         return defaultUserHomeImpl();
  1166.     }

  1167.     private File defaultUserHomeImpl() {
  1168.         final String home = AccessController.doPrivileged(
  1169.                 (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
  1170.         );
  1171.         if (home == null || home.length() == 0)
  1172.             return null;
  1173.         return new File(home).getAbsoluteFile();
  1174.     }

  1175.     /**
  1176.      * Searches the given path to see if it contains one of the given files.
  1177.      * Returns the first it finds. Returns null if not found or if path is null.
  1178.      *
  1179.      * @param path
  1180.      *            List of paths to search separated by File.pathSeparator
  1181.      * @param lookFor
  1182.      *            Files to search for in the given path
  1183.      * @return the first match found, or null
  1184.      * @since 3.0
  1185.      */
  1186.     protected static File searchPath(String path, String... lookFor) {
  1187.         if (path == null)
  1188.             return null;

  1189.         for (String p : path.split(File.pathSeparator)) {
  1190.             for (String command : lookFor) {
  1191.                 final File file = new File(p, command);
  1192.                 try {
  1193.                     if (file.isFile()) {
  1194.                         return file.getAbsoluteFile();
  1195.                     }
  1196.                 } catch (SecurityException e) {
  1197.                     LOG.warn(MessageFormat.format(
  1198.                             JGitText.get().skipNotAccessiblePath,
  1199.                             file.getPath()));
  1200.                 }
  1201.             }
  1202.         }
  1203.         return null;
  1204.     }

  1205.     /**
  1206.      * Execute a command and return a single line of output as a String
  1207.      *
  1208.      * @param dir
  1209.      *            Working directory for the command
  1210.      * @param command
  1211.      *            as component array
  1212.      * @param encoding
  1213.      *            to be used to parse the command's output
  1214.      * @return the one-line output of the command or {@code null} if there is
  1215.      *         none
  1216.      * @throws org.eclipse.jgit.errors.CommandFailedException
  1217.      *             thrown when the command failed (return code was non-zero)
  1218.      */
  1219.     @Nullable
  1220.     protected static String readPipe(File dir, String[] command,
  1221.             String encoding) throws CommandFailedException {
  1222.         return readPipe(dir, command, encoding, null);
  1223.     }

  1224.     /**
  1225.      * Execute a command and return a single line of output as a String
  1226.      *
  1227.      * @param dir
  1228.      *            Working directory for the command
  1229.      * @param command
  1230.      *            as component array
  1231.      * @param encoding
  1232.      *            to be used to parse the command's output
  1233.      * @param env
  1234.      *            Map of environment variables to be merged with those of the
  1235.      *            current process
  1236.      * @return the one-line output of the command or {@code null} if there is
  1237.      *         none
  1238.      * @throws org.eclipse.jgit.errors.CommandFailedException
  1239.      *             thrown when the command failed (return code was non-zero)
  1240.      * @since 4.0
  1241.      */
  1242.     @Nullable
  1243.     protected static String readPipe(File dir, String[] command,
  1244.             String encoding, Map<String, String> env)
  1245.             throws CommandFailedException {
  1246.         final boolean debug = LOG.isDebugEnabled();
  1247.         try {
  1248.             if (debug) {
  1249.                 LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  1250.                         + dir);
  1251.             }
  1252.             ProcessBuilder pb = new ProcessBuilder(command);
  1253.             pb.directory(dir);
  1254.             if (env != null) {
  1255.                 pb.environment().putAll(env);
  1256.             }
  1257.             Process p;
  1258.             try {
  1259.                 p = pb.start();
  1260.             } catch (IOException e) {
  1261.                 // Process failed to start
  1262.                 throw new CommandFailedException(-1, e.getMessage(), e);
  1263.             }
  1264.             p.getOutputStream().close();
  1265.             GobblerThread gobbler = new GobblerThread(p, command, dir);
  1266.             gobbler.start();
  1267.             String r = null;
  1268.             try (BufferedReader lineRead = new BufferedReader(
  1269.                     new InputStreamReader(p.getInputStream(), encoding))) {
  1270.                 r = lineRead.readLine();
  1271.                 if (debug) {
  1272.                     LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  1273.                     LOG.debug("remaining output:\n"); //$NON-NLS-1$
  1274.                     String l;
  1275.                     while ((l = lineRead.readLine()) != null) {
  1276.                         LOG.debug(l);
  1277.                     }
  1278.                 }
  1279.             }

  1280.             for (;;) {
  1281.                 try {
  1282.                     int rc = p.waitFor();
  1283.                     gobbler.join();
  1284.                     if (rc == 0 && !gobbler.fail.get()) {
  1285.                         return r;
  1286.                     }
  1287.                     if (debug) {
  1288.                         LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  1289.                     }
  1290.                     throw new CommandFailedException(rc,
  1291.                             gobbler.errorMessage.get(),
  1292.                             gobbler.exception.get());
  1293.                 } catch (InterruptedException ie) {
  1294.                     // Stop bothering me, I have a zombie to reap.
  1295.                 }
  1296.             }
  1297.         } catch (IOException e) {
  1298.             LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  1299.         } catch (AccessControlException e) {
  1300.             LOG.warn(MessageFormat.format(
  1301.                     JGitText.get().readPipeIsNotAllowedRequiredPermission,
  1302.                     command, dir, e.getPermission()));
  1303.         } catch (SecurityException e) {
  1304.             LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
  1305.                     command, dir));
  1306.         }
  1307.         if (debug) {
  1308.             LOG.debug("readpipe returns null"); //$NON-NLS-1$
  1309.         }
  1310.         return null;
  1311.     }

  1312.     private static class GobblerThread extends Thread {

  1313.         /* The process has 5 seconds to exit after closing stderr */
  1314.         private static final int PROCESS_EXIT_TIMEOUT = 5;

  1315.         private final Process p;
  1316.         private final String desc;
  1317.         private final String dir;
  1318.         final AtomicBoolean fail = new AtomicBoolean();
  1319.         final AtomicReference<String> errorMessage = new AtomicReference<>();
  1320.         final AtomicReference<Throwable> exception = new AtomicReference<>();

  1321.         GobblerThread(Process p, String[] command, File dir) {
  1322.             this.p = p;
  1323.             this.desc = Arrays.toString(command);
  1324.             this.dir = Objects.toString(dir);
  1325.         }

  1326.         @Override
  1327.         public void run() {
  1328.             StringBuilder err = new StringBuilder();
  1329.             try (InputStream is = p.getErrorStream()) {
  1330.                 int ch;
  1331.                 while ((ch = is.read()) != -1) {
  1332.                     err.append((char) ch);
  1333.                 }
  1334.             } catch (IOException e) {
  1335.                 if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  1336.                     setError(e, e.getMessage(), p.exitValue());
  1337.                     fail.set(true);
  1338.                 } else {
  1339.                     // ignore. command terminated faster and stream was just closed
  1340.                     // or the process didn't terminate within timeout
  1341.                 }
  1342.             } finally {
  1343.                 if (waitForProcessCompletion(null) && err.length() > 0) {
  1344.                     setError(null, err.toString(), p.exitValue());
  1345.                     if (p.exitValue() != 0) {
  1346.                         fail.set(true);
  1347.                     }
  1348.                 }
  1349.             }
  1350.         }

  1351.         @SuppressWarnings("boxing")
  1352.         private boolean waitForProcessCompletion(IOException originalError) {
  1353.             try {
  1354.                 if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  1355.                     setError(originalError, MessageFormat.format(
  1356.                             JGitText.get().commandClosedStderrButDidntExit,
  1357.                             desc, PROCESS_EXIT_TIMEOUT), -1);
  1358.                     fail.set(true);
  1359.                     return false;
  1360.                 }
  1361.             } catch (InterruptedException e) {
  1362.                 setError(originalError, MessageFormat.format(
  1363.                         JGitText.get().threadInterruptedWhileRunning, desc), -1);
  1364.                 fail.set(true);
  1365.                 return false;
  1366.             }
  1367.             return true;
  1368.         }

  1369.         private void setError(IOException e, String message, int exitCode) {
  1370.             exception.set(e);
  1371.             errorMessage.set(MessageFormat.format(
  1372.                     JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  1373.                     desc, dir, Integer.valueOf(exitCode), message));
  1374.         }
  1375.     }

  1376.     /**
  1377.      * Discover the path to the Git executable.
  1378.      *
  1379.      * @return the path to the Git executable or {@code null} if it cannot be
  1380.      *         determined.
  1381.      * @since 4.0
  1382.      */
  1383.     protected abstract File discoverGitExe();

  1384.     /**
  1385.      * Discover the path to the system-wide Git configuration file
  1386.      *
  1387.      * @return the path to the system-wide Git configuration file or
  1388.      *         {@code null} if it cannot be determined.
  1389.      * @since 4.0
  1390.      */
  1391.     protected File discoverGitSystemConfig() {
  1392.         File gitExe = discoverGitExe();
  1393.         if (gitExe == null) {
  1394.             return null;
  1395.         }

  1396.         // Bug 480782: Check if the discovered git executable is JGit CLI
  1397.         String v;
  1398.         try {
  1399.             v = readPipe(gitExe.getParentFile(),
  1400.                     new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$
  1401.                 Charset.defaultCharset().name());
  1402.         } catch (CommandFailedException e) {
  1403.             LOG.warn(e.getMessage());
  1404.             return null;
  1405.         }
  1406.         if (StringUtils.isEmptyOrNull(v)
  1407.                 || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  1408.             return null;
  1409.         }

  1410.         // Trick Git into printing the path to the config file by using "echo"
  1411.         // as the editor.
  1412.         Map<String, String> env = new HashMap<>();
  1413.         env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$

  1414.         String w;
  1415.         try {
  1416.             w = readPipe(gitExe.getParentFile(),
  1417.                     new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
  1418.                             "--edit" }, //$NON-NLS-1$
  1419.                 Charset.defaultCharset().name(), env);
  1420.         } catch (CommandFailedException e) {
  1421.             LOG.warn(e.getMessage());
  1422.             return null;
  1423.         }
  1424.         if (StringUtils.isEmptyOrNull(w)) {
  1425.             return null;
  1426.         }

  1427.         return new File(w);
  1428.     }

  1429.     /**
  1430.      * Get the currently used path to the system-wide Git configuration file.
  1431.      *
  1432.      * @return the currently used path to the system-wide Git configuration file
  1433.      *         or {@code null} if none has been set.
  1434.      * @since 4.0
  1435.      */
  1436.     public File getGitSystemConfig() {
  1437.         if (gitSystemConfig == null) {
  1438.             gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  1439.         }
  1440.         return gitSystemConfig.value;
  1441.     }

  1442.     /**
  1443.      * Set the path to the system-wide Git configuration file to use.
  1444.      *
  1445.      * @param configFile
  1446.      *            the path to the config file.
  1447.      * @return {@code this}
  1448.      * @since 4.0
  1449.      */
  1450.     public FS setGitSystemConfig(File configFile) {
  1451.         gitSystemConfig = new Holder<>(configFile);
  1452.         return this;
  1453.     }

  1454.     /**
  1455.      * Get the parent directory of this file's parent directory
  1456.      *
  1457.      * @param grandchild
  1458.      *            a {@link java.io.File} object.
  1459.      * @return the parent directory of this file's parent directory or
  1460.      *         {@code null} in case there's no grandparent directory
  1461.      * @since 4.0
  1462.      */
  1463.     protected static File resolveGrandparentFile(File grandchild) {
  1464.         if (grandchild != null) {
  1465.             File parent = grandchild.getParentFile();
  1466.             if (parent != null)
  1467.                 return parent.getParentFile();
  1468.         }
  1469.         return null;
  1470.     }

  1471.     /**
  1472.      * Check if a file is a symbolic link and read it
  1473.      *
  1474.      * @param path
  1475.      *            a {@link java.io.File} object.
  1476.      * @return target of link or null
  1477.      * @throws java.io.IOException
  1478.      * @since 3.0
  1479.      */
  1480.     public String readSymLink(File path) throws IOException {
  1481.         return FileUtils.readSymLink(path);
  1482.     }

  1483.     /**
  1484.      * Whether the path is a symbolic link (and we support these).
  1485.      *
  1486.      * @param path
  1487.      *            a {@link java.io.File} object.
  1488.      * @return true if the path is a symbolic link (and we support these)
  1489.      * @throws java.io.IOException
  1490.      * @since 3.0
  1491.      */
  1492.     public boolean isSymLink(File path) throws IOException {
  1493.         return FileUtils.isSymlink(path);
  1494.     }

  1495.     /**
  1496.      * Tests if the path exists, in case of a symbolic link, true even if the
  1497.      * target does not exist
  1498.      *
  1499.      * @param path
  1500.      *            a {@link java.io.File} object.
  1501.      * @return true if path exists
  1502.      * @since 3.0
  1503.      */
  1504.     public boolean exists(File path) {
  1505.         return FileUtils.exists(path);
  1506.     }

  1507.     /**
  1508.      * Check if path is a directory. If the OS/JRE supports symbolic links and
  1509.      * path is a symbolic link to a directory, this method returns false.
  1510.      *
  1511.      * @param path
  1512.      *            a {@link java.io.File} object.
  1513.      * @return true if file is a directory,
  1514.      * @since 3.0
  1515.      */
  1516.     public boolean isDirectory(File path) {
  1517.         return FileUtils.isDirectory(path);
  1518.     }

  1519.     /**
  1520.      * Examine if path represents a regular file. If the OS/JRE supports
  1521.      * symbolic links the test returns false if path represents a symbolic link.
  1522.      *
  1523.      * @param path
  1524.      *            a {@link java.io.File} object.
  1525.      * @return true if path represents a regular file
  1526.      * @since 3.0
  1527.      */
  1528.     public boolean isFile(File path) {
  1529.         return FileUtils.isFile(path);
  1530.     }

  1531.     /**
  1532.      * Whether path is hidden, either starts with . on unix or has the hidden
  1533.      * attribute in windows
  1534.      *
  1535.      * @param path
  1536.      *            a {@link java.io.File} object.
  1537.      * @return true if path is hidden, either starts with . on unix or has the
  1538.      *         hidden attribute in windows
  1539.      * @throws java.io.IOException
  1540.      * @since 3.0
  1541.      */
  1542.     public boolean isHidden(File path) throws IOException {
  1543.         return FileUtils.isHidden(path);
  1544.     }

  1545.     /**
  1546.      * Set the hidden attribute for file whose name starts with a period.
  1547.      *
  1548.      * @param path
  1549.      *            a {@link java.io.File} object.
  1550.      * @param hidden
  1551.      *            whether to set the file hidden
  1552.      * @throws java.io.IOException
  1553.      * @since 3.0
  1554.      */
  1555.     public void setHidden(File path, boolean hidden) throws IOException {
  1556.         FileUtils.setHidden(path, hidden);
  1557.     }

  1558.     /**
  1559.      * Create a symbolic link
  1560.      *
  1561.      * @param path
  1562.      *            a {@link java.io.File} object.
  1563.      * @param target
  1564.      *            target path of the symlink
  1565.      * @throws java.io.IOException
  1566.      * @since 3.0
  1567.      */
  1568.     public void createSymLink(File path, String target) throws IOException {
  1569.         FileUtils.createSymLink(path, target);
  1570.     }

  1571.     /**
  1572.      * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1573.      * of this class may take care to provide a safe implementation for this
  1574.      * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1575.      *
  1576.      * @param path
  1577.      *            the file to be created
  1578.      * @return <code>true</code> if the file was created, <code>false</code> if
  1579.      *         the file already existed
  1580.      * @throws java.io.IOException
  1581.      * @deprecated use {@link #createNewFileAtomic(File)} instead
  1582.      * @since 4.5
  1583.      */
  1584.     @Deprecated
  1585.     public boolean createNewFile(File path) throws IOException {
  1586.         return path.createNewFile();
  1587.     }

  1588.     /**
  1589.      * A token representing a file created by
  1590.      * {@link #createNewFileAtomic(File)}. The token must be retained until the
  1591.      * file has been deleted in order to guarantee that the unique file was
  1592.      * created atomically. As soon as the file is no longer needed the lock
  1593.      * token must be closed.
  1594.      *
  1595.      * @since 4.7
  1596.      */
  1597.     public static class LockToken implements Closeable {
  1598.         private boolean isCreated;

  1599.         private Optional<Path> link;

  1600.         LockToken(boolean isCreated, Optional<Path> link) {
  1601.             this.isCreated = isCreated;
  1602.             this.link = link;
  1603.         }

  1604.         /**
  1605.          * @return {@code true} if the file was created successfully
  1606.          */
  1607.         public boolean isCreated() {
  1608.             return isCreated;
  1609.         }

  1610.         @Override
  1611.         public void close() {
  1612.             if (!link.isPresent()) {
  1613.                 return;
  1614.             }
  1615.             Path p = link.get();
  1616.             if (!Files.exists(p)) {
  1617.                 return;
  1618.             }
  1619.             try {
  1620.                 Files.delete(p);
  1621.             } catch (IOException e) {
  1622.                 LOG.error(MessageFormat
  1623.                         .format(JGitText.get().closeLockTokenFailed, this), e);
  1624.             }
  1625.         }

  1626.         @Override
  1627.         public String toString() {
  1628.             return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
  1629.                     ", link=" //$NON-NLS-1$
  1630.                     + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
  1631.                             : "<null>]"); //$NON-NLS-1$
  1632.         }
  1633.     }

  1634.     /**
  1635.      * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1636.      * of this class may take care to provide a safe implementation for this
  1637.      * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1638.      *
  1639.      * @param path
  1640.      *            the file to be created
  1641.      * @return LockToken this token must be closed after the created file was
  1642.      *         deleted
  1643.      * @throws IOException
  1644.      * @since 4.7
  1645.      */
  1646.     public LockToken createNewFileAtomic(File path) throws IOException {
  1647.         return new LockToken(path.createNewFile(), Optional.empty());
  1648.     }

  1649.     /**
  1650.      * See
  1651.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  1652.      *
  1653.      * @param base
  1654.      *            The path against which <code>other</code> should be
  1655.      *            relativized.
  1656.      * @param other
  1657.      *            The path that will be made relative to <code>base</code>.
  1658.      * @return A relative path that, when resolved against <code>base</code>,
  1659.      *         will yield the original <code>other</code>.
  1660.      * @see FileUtils#relativizePath(String, String, String, boolean)
  1661.      * @since 3.7
  1662.      */
  1663.     public String relativize(String base, String other) {
  1664.         return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  1665.     }

  1666.     /**
  1667.      * Enumerates children of a directory.
  1668.      *
  1669.      * @param directory
  1670.      *            to get the children of
  1671.      * @param fileModeStrategy
  1672.      *            to use to calculate the git mode of a child
  1673.      * @return an array of entries for the children
  1674.      *
  1675.      * @since 5.0
  1676.      */
  1677.     public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
  1678.         final File[] all = directory.listFiles();
  1679.         if (all == null) {
  1680.             return NO_ENTRIES;
  1681.         }
  1682.         final Entry[] result = new Entry[all.length];
  1683.         for (int i = 0; i < result.length; i++) {
  1684.             result[i] = new FileEntry(all[i], this, fileModeStrategy);
  1685.         }
  1686.         return result;
  1687.     }

  1688.     /**
  1689.      * Checks whether the given hook is defined for the given repository, then
  1690.      * runs it with the given arguments.
  1691.      * <p>
  1692.      * The hook's standard output and error streams will be redirected to
  1693.      * <code>System.out</code> and <code>System.err</code> respectively. The
  1694.      * hook will have no stdin.
  1695.      * </p>
  1696.      *
  1697.      * @param repository
  1698.      *            The repository for which a hook should be run.
  1699.      * @param hookName
  1700.      *            The name of the hook to be executed.
  1701.      * @param args
  1702.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1703.      *            but can be an empty array.
  1704.      * @return The ProcessResult describing this hook's execution.
  1705.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1706.      *             if we fail to run the hook somehow. Causes may include an
  1707.      *             interrupted process or I/O errors.
  1708.      * @since 4.0
  1709.      */
  1710.     public ProcessResult runHookIfPresent(Repository repository,
  1711.             final String hookName,
  1712.             String[] args) throws JGitInternalException {
  1713.         return runHookIfPresent(repository, hookName, args, System.out, System.err,
  1714.                 null);
  1715.     }

  1716.     /**
  1717.      * Checks whether the given hook is defined for the given repository, then
  1718.      * runs it with the given arguments.
  1719.      *
  1720.      * @param repository
  1721.      *            The repository for which a hook should be run.
  1722.      * @param hookName
  1723.      *            The name of the hook to be executed.
  1724.      * @param args
  1725.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1726.      *            but can be an empty array.
  1727.      * @param outRedirect
  1728.      *            A print stream on which to redirect the hook's stdout. Can be
  1729.      *            <code>null</code>, in which case the hook's standard output
  1730.      *            will be lost.
  1731.      * @param errRedirect
  1732.      *            A print stream on which to redirect the hook's stderr. Can be
  1733.      *            <code>null</code>, in which case the hook's standard error
  1734.      *            will be lost.
  1735.      * @param stdinArgs
  1736.      *            A string to pass on to the standard input of the hook. May be
  1737.      *            <code>null</code>.
  1738.      * @return The ProcessResult describing this hook's execution.
  1739.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1740.      *             if we fail to run the hook somehow. Causes may include an
  1741.      *             interrupted process or I/O errors.
  1742.      * @since 5.11
  1743.      */
  1744.     public ProcessResult runHookIfPresent(Repository repository,
  1745.             final String hookName,
  1746.             String[] args, OutputStream outRedirect, OutputStream errRedirect,
  1747.             String stdinArgs) throws JGitInternalException {
  1748.         return new ProcessResult(Status.NOT_SUPPORTED);
  1749.     }

  1750.     /**
  1751.      * See
  1752.      * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, String)}
  1753.      * . Should only be called by FS supporting shell scripts execution.
  1754.      *
  1755.      * @param repository
  1756.      *            The repository for which a hook should be run.
  1757.      * @param hookName
  1758.      *            The name of the hook to be executed.
  1759.      * @param args
  1760.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1761.      *            but can be an empty array.
  1762.      * @param outRedirect
  1763.      *            A print stream on which to redirect the hook's stdout. Can be
  1764.      *            <code>null</code>, in which case the hook's standard output
  1765.      *            will be lost.
  1766.      * @param errRedirect
  1767.      *            A print stream on which to redirect the hook's stderr. Can be
  1768.      *            <code>null</code>, in which case the hook's standard error
  1769.      *            will be lost.
  1770.      * @param stdinArgs
  1771.      *            A string to pass on to the standard input of the hook. May be
  1772.      *            <code>null</code>.
  1773.      * @return The ProcessResult describing this hook's execution.
  1774.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1775.      *             if we fail to run the hook somehow. Causes may include an
  1776.      *             interrupted process or I/O errors.
  1777.      * @since 5.11
  1778.      */
  1779.     protected ProcessResult internalRunHookIfPresent(Repository repository,
  1780.             final String hookName, String[] args, OutputStream outRedirect,
  1781.             OutputStream errRedirect, String stdinArgs)
  1782.             throws JGitInternalException {
  1783.         File hookFile = findHook(repository, hookName);
  1784.         if (hookFile == null || hookName == null) {
  1785.             return new ProcessResult(Status.NOT_PRESENT);
  1786.         }

  1787.         File runDirectory = getRunDirectory(repository, hookName);
  1788.         if (runDirectory == null) {
  1789.             return new ProcessResult(Status.NOT_PRESENT);
  1790.         }
  1791.         String cmd = hookFile.getAbsolutePath();
  1792.         ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
  1793.         hookProcess.directory(runDirectory.getAbsoluteFile());
  1794.         Map<String, String> environment = hookProcess.environment();
  1795.         environment.put(Constants.GIT_DIR_KEY,
  1796.                 repository.getDirectory().getAbsolutePath());
  1797.         if (!repository.isBare()) {
  1798.             environment.put(Constants.GIT_WORK_TREE_KEY,
  1799.                     repository.getWorkTree().getAbsolutePath());
  1800.         }
  1801.         try {
  1802.             return new ProcessResult(runProcess(hookProcess, outRedirect,
  1803.                     errRedirect, stdinArgs), Status.OK);
  1804.         } catch (IOException e) {
  1805.             throw new JGitInternalException(MessageFormat.format(
  1806.                     JGitText.get().exceptionCaughtDuringExecutionOfHook,
  1807.                     hookName), e);
  1808.         } catch (InterruptedException e) {
  1809.             throw new JGitInternalException(MessageFormat.format(
  1810.                     JGitText.get().exceptionHookExecutionInterrupted,
  1811.                             hookName), e);
  1812.         }
  1813.     }

  1814.     /**
  1815.      * Quote a string (such as a file system path obtained from a Java
  1816.      * {@link File} or {@link Path} object) such that it can be passed as first
  1817.      * argument to {@link #runInShell(String, String[])}.
  1818.      * <p>
  1819.      * This default implementation returns the string unchanged.
  1820.      * </p>
  1821.      *
  1822.      * @param cmd
  1823.      *            the String to quote
  1824.      * @return the quoted string
  1825.      */
  1826.     String shellQuote(String cmd) {
  1827.         return cmd;
  1828.     }

  1829.     /**
  1830.      * Tries to find a hook matching the given one in the given repository.
  1831.      *
  1832.      * @param repository
  1833.      *            The repository within which to find a hook.
  1834.      * @param hookName
  1835.      *            The name of the hook we're trying to find.
  1836.      * @return The {@link java.io.File} containing this particular hook if it
  1837.      *         exists in the given repository, <code>null</code> otherwise.
  1838.      * @since 4.0
  1839.      */
  1840.     public File findHook(Repository repository, String hookName) {
  1841.         if (hookName == null) {
  1842.             return null;
  1843.         }
  1844.         File hookDir = getHooksDirectory(repository);
  1845.         if (hookDir == null) {
  1846.             return null;
  1847.         }
  1848.         File hookFile = new File(hookDir, hookName);
  1849.         if (hookFile.isAbsolute()) {
  1850.             if (!hookFile.exists() || (FS.DETECTED.supportsExecute()
  1851.                     && !FS.DETECTED.canExecute(hookFile))) {
  1852.                 return null;
  1853.             }
  1854.         } else {
  1855.             try {
  1856.                 File runDirectory = getRunDirectory(repository, hookName);
  1857.                 if (runDirectory == null) {
  1858.                     return null;
  1859.                 }
  1860.                 Path hookPath = runDirectory.getAbsoluteFile().toPath()
  1861.                         .resolve(hookFile.toPath());
  1862.                 FS fs = repository.getFS();
  1863.                 if (fs == null) {
  1864.                     fs = FS.DETECTED;
  1865.                 }
  1866.                 if (!Files.exists(hookPath) || (fs.supportsExecute()
  1867.                         && !fs.canExecute(hookPath.toFile()))) {
  1868.                     return null;
  1869.                 }
  1870.                 hookFile = hookPath.toFile();
  1871.             } catch (InvalidPathException e) {
  1872.                 LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath,
  1873.                         hookFile));
  1874.                 return null;
  1875.             }
  1876.         }
  1877.         return hookFile;
  1878.     }

  1879.     private File getRunDirectory(Repository repository,
  1880.             @NonNull String hookName) {
  1881.         if (repository.isBare()) {
  1882.             return repository.getDirectory();
  1883.         }
  1884.         switch (hookName) {
  1885.         case "pre-receive": //$NON-NLS-1$
  1886.         case "update": //$NON-NLS-1$
  1887.         case "post-receive": //$NON-NLS-1$
  1888.         case "post-update": //$NON-NLS-1$
  1889.         case "push-to-checkout": //$NON-NLS-1$
  1890.             return repository.getDirectory();
  1891.         default:
  1892.             return repository.getWorkTree();
  1893.         }
  1894.     }

  1895.     private File getHooksDirectory(Repository repository) {
  1896.         Config config = repository.getConfig();
  1897.         String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
  1898.                 null, ConfigConstants.CONFIG_KEY_HOOKS_PATH);
  1899.         if (hooksDir != null) {
  1900.             return new File(hooksDir);
  1901.         }
  1902.         File dir = repository.getDirectory();
  1903.         return dir == null ? null : new File(dir, Constants.HOOKS);
  1904.     }

  1905.     /**
  1906.      * Runs the given process until termination, clearing its stdout and stderr
  1907.      * streams on-the-fly.
  1908.      *
  1909.      * @param processBuilder
  1910.      *            The process builder configured for this process.
  1911.      * @param outRedirect
  1912.      *            A OutputStream on which to redirect the processes stdout. Can
  1913.      *            be <code>null</code>, in which case the processes standard
  1914.      *            output will be lost.
  1915.      * @param errRedirect
  1916.      *            A OutputStream on which to redirect the processes stderr. Can
  1917.      *            be <code>null</code>, in which case the processes standard
  1918.      *            error will be lost.
  1919.      * @param stdinArgs
  1920.      *            A string to pass on to the standard input of the hook. Can be
  1921.      *            <code>null</code>.
  1922.      * @return the exit value of this process.
  1923.      * @throws java.io.IOException
  1924.      *             if an I/O error occurs while executing this process.
  1925.      * @throws java.lang.InterruptedException
  1926.      *             if the current thread is interrupted while waiting for the
  1927.      *             process to end.
  1928.      * @since 4.2
  1929.      */
  1930.     public int runProcess(ProcessBuilder processBuilder,
  1931.             OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  1932.             throws IOException, InterruptedException {
  1933.         InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  1934.                         stdinArgs.getBytes(UTF_8));
  1935.         return runProcess(processBuilder, outRedirect, errRedirect, in);
  1936.     }

  1937.     /**
  1938.      * Runs the given process until termination, clearing its stdout and stderr
  1939.      * streams on-the-fly.
  1940.      *
  1941.      * @param processBuilder
  1942.      *            The process builder configured for this process.
  1943.      * @param outRedirect
  1944.      *            An OutputStream on which to redirect the processes stdout. Can
  1945.      *            be <code>null</code>, in which case the processes standard
  1946.      *            output will be lost.
  1947.      * @param errRedirect
  1948.      *            An OutputStream on which to redirect the processes stderr. Can
  1949.      *            be <code>null</code>, in which case the processes standard
  1950.      *            error will be lost.
  1951.      * @param inRedirect
  1952.      *            An InputStream from which to redirect the processes stdin. Can
  1953.      *            be <code>null</code>, in which case the process doesn't get
  1954.      *            any data over stdin. It is assumed that the whole InputStream
  1955.      *            will be consumed by the process. The method will close the
  1956.      *            inputstream after all bytes are read.
  1957.      * @return the return code of this process.
  1958.      * @throws java.io.IOException
  1959.      *             if an I/O error occurs while executing this process.
  1960.      * @throws java.lang.InterruptedException
  1961.      *             if the current thread is interrupted while waiting for the
  1962.      *             process to end.
  1963.      * @since 4.2
  1964.      */
  1965.     public int runProcess(ProcessBuilder processBuilder,
  1966.             OutputStream outRedirect, OutputStream errRedirect,
  1967.             InputStream inRedirect) throws IOException,
  1968.             InterruptedException {
  1969.         final ExecutorService executor = Executors.newFixedThreadPool(2);
  1970.         Process process = null;
  1971.         // We'll record the first I/O exception that occurs, but keep on trying
  1972.         // to dispose of our open streams and file handles
  1973.         IOException ioException = null;
  1974.         try {
  1975.             process = processBuilder.start();
  1976.             executor.execute(
  1977.                     new StreamGobbler(process.getErrorStream(), errRedirect));
  1978.             executor.execute(
  1979.                     new StreamGobbler(process.getInputStream(), outRedirect));
  1980.             @SuppressWarnings("resource") // Closed in the finally block
  1981.             OutputStream outputStream = process.getOutputStream();
  1982.             try {
  1983.                 if (inRedirect != null) {
  1984.                     new StreamGobbler(inRedirect, outputStream).copy();
  1985.                 }
  1986.             } finally {
  1987.                 try {
  1988.                     outputStream.close();
  1989.                 } catch (IOException e) {
  1990.                     // When the process exits before consuming the input, the OutputStream
  1991.                     // is replaced with the null output stream. This null output stream
  1992.                     // throws IOException for all write calls. When StreamGobbler fails to
  1993.                     // flush the buffer because of this, this close call tries to flush it
  1994.                     // again. This causes another IOException. Since we ignore the
  1995.                     // IOException in StreamGobbler, we also ignore the exception here.
  1996.                 }
  1997.             }
  1998.             return process.waitFor();
  1999.         } catch (IOException e) {
  2000.             ioException = e;
  2001.         } finally {
  2002.             shutdownAndAwaitTermination(executor);
  2003.             if (process != null) {
  2004.                 try {
  2005.                     process.waitFor();
  2006.                 } catch (InterruptedException e) {
  2007.                     // Thrown by the outer try.
  2008.                     // Swallow this one to carry on our cleanup, and clear the
  2009.                     // interrupted flag (processes throw the exception without
  2010.                     // clearing the flag).
  2011.                     Thread.interrupted();
  2012.                 }
  2013.                 // A process doesn't clean its own resources even when destroyed
  2014.                 // Explicitly try and close all three streams, preserving the
  2015.                 // outer I/O exception if any.
  2016.                 if (inRedirect != null) {
  2017.                     inRedirect.close();
  2018.                 }
  2019.                 try {
  2020.                     process.getErrorStream().close();
  2021.                 } catch (IOException e) {
  2022.                     ioException = ioException != null ? ioException : e;
  2023.                 }
  2024.                 try {
  2025.                     process.getInputStream().close();
  2026.                 } catch (IOException e) {
  2027.                     ioException = ioException != null ? ioException : e;
  2028.                 }
  2029.                 try {
  2030.                     process.getOutputStream().close();
  2031.                 } catch (IOException e) {
  2032.                     ioException = ioException != null ? ioException : e;
  2033.                 }
  2034.                 process.destroy();
  2035.             }
  2036.         }
  2037.         // We can only be here if the outer try threw an IOException.
  2038.         throw ioException;
  2039.     }

  2040.     /**
  2041.      * Shuts down an {@link ExecutorService} in two phases, first by calling
  2042.      * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  2043.      * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  2044.      * necessary, to cancel any lingering tasks. Returns true if the pool has
  2045.      * been properly shutdown, false otherwise.
  2046.      * <p>
  2047.      *
  2048.      * @param pool
  2049.      *            the pool to shutdown
  2050.      * @return <code>true</code> if the pool has been properly shutdown,
  2051.      *         <code>false</code> otherwise.
  2052.      */
  2053.     private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  2054.         boolean hasShutdown = true;
  2055.         pool.shutdown(); // Disable new tasks from being submitted
  2056.         try {
  2057.             // Wait a while for existing tasks to terminate
  2058.             if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  2059.                 pool.shutdownNow(); // Cancel currently executing tasks
  2060.                 // Wait a while for tasks to respond to being canceled
  2061.                 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  2062.                     hasShutdown = false;
  2063.             }
  2064.         } catch (InterruptedException ie) {
  2065.             // (Re-)Cancel if current thread also interrupted
  2066.             pool.shutdownNow();
  2067.             // Preserve interrupt status
  2068.             Thread.currentThread().interrupt();
  2069.             hasShutdown = false;
  2070.         }
  2071.         return hasShutdown;
  2072.     }

  2073.     /**
  2074.      * Initialize a ProcessBuilder to run a command using the system shell.
  2075.      *
  2076.      * @param cmd
  2077.      *            command to execute. This string should originate from the
  2078.      *            end-user, and thus is platform specific.
  2079.      * @param args
  2080.      *            arguments to pass to command. These should be protected from
  2081.      *            shell evaluation.
  2082.      * @return a partially completed process builder. Caller should finish
  2083.      *         populating directory, environment, and then start the process.
  2084.      */
  2085.     public abstract ProcessBuilder runInShell(String cmd, String[] args);

  2086.     /**
  2087.      * Execute a command defined by a {@link java.lang.ProcessBuilder}.
  2088.      *
  2089.      * @param pb
  2090.      *            The command to be executed
  2091.      * @param in
  2092.      *            The standard input stream passed to the process
  2093.      * @return The result of the executed command
  2094.      * @throws java.lang.InterruptedException
  2095.      * @throws java.io.IOException
  2096.      * @since 4.2
  2097.      */
  2098.     public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  2099.             throws IOException, InterruptedException {
  2100.         try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  2101.                 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
  2102.                         1024 * 1024)) {
  2103.             int rc = runProcess(pb, stdout, stderr, in);
  2104.             return new ExecutionResult(stdout, stderr, rc);
  2105.         }
  2106.     }

  2107.     private static class Holder<V> {
  2108.         final V value;

  2109.         Holder(V value) {
  2110.             this.value = value;
  2111.         }
  2112.     }

  2113.     /**
  2114.      * File attributes we typically care for.
  2115.      *
  2116.      * @since 3.3
  2117.      */
  2118.     public static class Attributes {

  2119.         /**
  2120.          * @return true if this are the attributes of a directory
  2121.          */
  2122.         public boolean isDirectory() {
  2123.             return isDirectory;
  2124.         }

  2125.         /**
  2126.          * @return true if this are the attributes of an executable file
  2127.          */
  2128.         public boolean isExecutable() {
  2129.             return isExecutable;
  2130.         }

  2131.         /**
  2132.          * @return true if this are the attributes of a symbolic link
  2133.          */
  2134.         public boolean isSymbolicLink() {
  2135.             return isSymbolicLink;
  2136.         }

  2137.         /**
  2138.          * @return true if this are the attributes of a regular file
  2139.          */
  2140.         public boolean isRegularFile() {
  2141.             return isRegularFile;
  2142.         }

  2143.         /**
  2144.          * @return the time when the file was created
  2145.          */
  2146.         public long getCreationTime() {
  2147.             return creationTime;
  2148.         }

  2149.         /**
  2150.          * @return the time (milliseconds since 1970-01-01) when this object was
  2151.          *         last modified
  2152.          * @deprecated use getLastModifiedInstant instead
  2153.          */
  2154.         @Deprecated
  2155.         public long getLastModifiedTime() {
  2156.             return lastModifiedInstant.toEpochMilli();
  2157.         }

  2158.         /**
  2159.          * @return the time when this object was last modified
  2160.          * @since 5.1.9
  2161.          */
  2162.         public Instant getLastModifiedInstant() {
  2163.             return lastModifiedInstant;
  2164.         }

  2165.         private final boolean isDirectory;

  2166.         private final boolean isSymbolicLink;

  2167.         private final boolean isRegularFile;

  2168.         private final long creationTime;

  2169.         private final Instant lastModifiedInstant;

  2170.         private final boolean isExecutable;

  2171.         private final File file;

  2172.         private final boolean exists;

  2173.         /**
  2174.          * file length
  2175.          */
  2176.         protected long length = -1;

  2177.         final FS fs;

  2178.         Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  2179.                 boolean isExecutable, boolean isSymbolicLink,
  2180.                 boolean isRegularFile, long creationTime,
  2181.                 Instant lastModifiedInstant, long length) {
  2182.             this.fs = fs;
  2183.             this.file = file;
  2184.             this.exists = exists;
  2185.             this.isDirectory = isDirectory;
  2186.             this.isExecutable = isExecutable;
  2187.             this.isSymbolicLink = isSymbolicLink;
  2188.             this.isRegularFile = isRegularFile;
  2189.             this.creationTime = creationTime;
  2190.             this.lastModifiedInstant = lastModifiedInstant;
  2191.             this.length = length;
  2192.         }

  2193.         /**
  2194.          * Constructor when there are issues with reading. All attributes except
  2195.          * given will be set to the default values.
  2196.          *
  2197.          * @param fs
  2198.          * @param path
  2199.          */
  2200.         public Attributes(File path, FS fs) {
  2201.             this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
  2202.         }

  2203.         /**
  2204.          * @return length of this file object
  2205.          */
  2206.         public long getLength() {
  2207.             if (length == -1)
  2208.                 return length = file.length();
  2209.             return length;
  2210.         }

  2211.         /**
  2212.          * @return the filename
  2213.          */
  2214.         public String getName() {
  2215.             return file.getName();
  2216.         }

  2217.         /**
  2218.          * @return the file the attributes apply to
  2219.          */
  2220.         public File getFile() {
  2221.             return file;
  2222.         }

  2223.         boolean exists() {
  2224.             return exists;
  2225.         }
  2226.     }

  2227.     /**
  2228.      * Get the file attributes we care for.
  2229.      *
  2230.      * @param path
  2231.      *            a {@link java.io.File} object.
  2232.      * @return the file attributes we care for.
  2233.      * @since 3.3
  2234.      */
  2235.     public Attributes getAttributes(File path) {
  2236.         boolean isDirectory = isDirectory(path);
  2237.         boolean isFile = !isDirectory && path.isFile();
  2238.         assert path.exists() == isDirectory || isFile;
  2239.         boolean exists = isDirectory || isFile;
  2240.         boolean canExecute = exists && !isDirectory && canExecute(path);
  2241.         boolean isSymlink = false;
  2242.         Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
  2243.         long createTime = 0L;
  2244.         return new Attributes(this, path, exists, isDirectory, canExecute,
  2245.                 isSymlink, isFile, createTime, lastModified, -1);
  2246.     }

  2247.     /**
  2248.      * Normalize the unicode path to composed form.
  2249.      *
  2250.      * @param file
  2251.      *            a {@link java.io.File} object.
  2252.      * @return NFC-format File
  2253.      * @since 3.3
  2254.      */
  2255.     public File normalize(File file) {
  2256.         return file;
  2257.     }

  2258.     /**
  2259.      * Normalize the unicode path to composed form.
  2260.      *
  2261.      * @param name
  2262.      *            path name
  2263.      * @return NFC-format string
  2264.      * @since 3.3
  2265.      */
  2266.     public String normalize(String name) {
  2267.         return name;
  2268.     }

  2269.     /**
  2270.      * This runnable will consume an input stream's content into an output
  2271.      * stream as soon as it gets available.
  2272.      * <p>
  2273.      * Typically used to empty processes' standard output and error, preventing
  2274.      * them to choke.
  2275.      * </p>
  2276.      * <p>
  2277.      * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  2278.      * streams.
  2279.      * </p>
  2280.      */
  2281.     private static class StreamGobbler implements Runnable {
  2282.         private InputStream in;

  2283.         private OutputStream out;

  2284.         public StreamGobbler(InputStream stream, OutputStream output) {
  2285.             this.in = stream;
  2286.             this.out = output;
  2287.         }

  2288.         @Override
  2289.         public void run() {
  2290.             try {
  2291.                 copy();
  2292.             } catch (IOException e) {
  2293.                 // Do nothing on read failure; leave streams open.
  2294.             }
  2295.         }

  2296.         void copy() throws IOException {
  2297.             boolean writeFailure = false;
  2298.             byte[] buffer = new byte[4096];
  2299.             int readBytes;
  2300.             while ((readBytes = in.read(buffer)) != -1) {
  2301.                 // Do not try to write again after a failure, but keep
  2302.                 // reading as long as possible to prevent the input stream
  2303.                 // from choking.
  2304.                 if (!writeFailure && out != null) {
  2305.                     try {
  2306.                         out.write(buffer, 0, readBytes);
  2307.                         out.flush();
  2308.                     } catch (IOException e) {
  2309.                         writeFailure = true;
  2310.                     }
  2311.                 }
  2312.             }
  2313.         }
  2314.     }
  2315. }