/*
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.util;

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

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
import org.eclipse.jgit.util.ProcessResult.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Abstraction to support various file system operations not in Java.
/** * Abstraction to support various file system operations not in Java. */
public abstract class FS { private static final Logger LOG = LoggerFactory.getLogger(FS.class);
An empty array of entries, suitable as a return value for list(File, FileModeStrategy).
Since:5.0
/** * An empty array of entries, suitable as a return value for * {@link #list(File, FileModeStrategy)}. * * @since 5.0 */
protected static final Entry[] NO_ENTRIES = {}; private volatile Boolean supportSymlinks;
This class creates FS instances. It will be overridden by a Java7 variant if such can be detected in detect(Boolean).
Since:3.0
/** * This class creates FS instances. It will be overridden by a Java7 variant * if such can be detected in {@link #detect(Boolean)}. * * @since 3.0 */
public static class FSFactory {
Constructor
/** * Constructor */
protected FSFactory() { // empty }
Detect the file system
Params:
  • cygwinUsed –
Returns:FS instance
/** * Detect the file system * * @param cygwinUsed * @return FS instance */
public FS detect(Boolean cygwinUsed) { if (SystemReader.getInstance().isWindows()) { if (cygwinUsed == null) cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin()); if (cygwinUsed.booleanValue()) return new FS_Win32_Cygwin(); else return new FS_Win32(); } else { return new FS_POSIX(); } } }
Result of an executed process. The caller is responsible to close the contained TemporaryBuffers
Since:4.2
/** * Result of an executed process. The caller is responsible to close the * contained {@link TemporaryBuffer}s * * @since 4.2 */
public static class ExecutionResult { private TemporaryBuffer stdout; private TemporaryBuffer stderr; private int rc;
Params:
  • stdout –
  • stderr –
  • rc –
/** * @param stdout * @param stderr * @param rc */
public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, int rc) { this.stdout = stdout; this.stderr = stderr; this.rc = rc; }
Returns:buffered standard output stream
/** * @return buffered standard output stream */
public TemporaryBuffer getStdout() { return stdout; }
Returns:buffered standard error stream
/** * @return buffered standard error stream */
public TemporaryBuffer getStderr() { return stderr; }
Returns:the return code of the process
/** * @return the return code of the process */
public int getRc() { return rc; } }
Attributes of FileStores on this system
Since:5.1.9
/** * Attributes of FileStores on this system * * @since 5.1.9 */
public final static class FileStoreAttributes { private static final Duration UNDEFINED_DURATION = Duration .ofNanos(Long.MAX_VALUE);
Fallback filesystem timestamp resolution. The worst case timestamp resolution on FAT filesystems is 2 seconds.
/** * Fallback filesystem timestamp resolution. The worst case timestamp * resolution on FAT filesystems is 2 seconds. */
public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration .ofMillis(2000);
Fallback FileStore attributes used when we can't measure the filesystem timestamp resolution. The last modified time granularity of FAT filesystems is 2 seconds.
/** * Fallback FileStore attributes used when we can't measure the * filesystem timestamp resolution. The last modified time granularity * of FAT filesystems is 2 seconds. */
public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes( FALLBACK_TIMESTAMP_RESOLUTION); private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>(); private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>( 100, 0.2f); private static AtomicBoolean background = new AtomicBoolean(); private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>(); private static void setBackground(boolean async) { background.set(async); } private static final String javaVersionPrefix = System .getProperty("java.vendor") + '|' //$NON-NLS-1$ + System.getProperty("java.version") + '|'; //$NON-NLS-1$ private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration .ofMillis(10);
Configures size and purge factor of the path-based cache for file system attributes. Caching of file system attributes avoids recurring lookup of @{code FileStore} of files which may be expensive on some platforms.
Params:
  • maxSize – maximum size of the cache, default is 100
  • purgeFactor – when the size of the map reaches maxSize the oldest entries will be purged to free up some space for new entries, purgeFactor is the fraction of maxSize to purge when this happens
Since:5.1.9
/** * Configures size and purge factor of the path-based cache for file * system attributes. Caching of file system attributes avoids recurring * lookup of @{code FileStore} of files which may be expensive on some * platforms. * * @param maxSize * maximum size of the cache, default is 100 * @param purgeFactor * when the size of the map reaches maxSize the oldest * entries will be purged to free up some space for new * entries, {@code purgeFactor} is the fraction of * {@code maxSize} to purge when this happens * @since 5.1.9 */
public static void configureAttributesPathCache(int maxSize, float purgeFactor) { FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor); }
Get the FileStoreAttributes for the given FileStore
Params:
  • path – file residing in the FileStore to get attributes for
Returns:FileStoreAttributes for the given path.
/** * Get the FileStoreAttributes for the given FileStore * * @param path * file residing in the FileStore to get attributes for * @return FileStoreAttributes for the given path. */
public static FileStoreAttributes get(Path path) { try { path = path.toAbsolutePath(); Path dir = Files.isDirectory(path) ? path : path.getParent(); FileStoreAttributes cached = attrCacheByPath.get(dir); if (cached != null) { return cached; } FileStoreAttributes attrs = getFileStoreAttributes(dir); attrCacheByPath.put(dir, attrs); return attrs; } catch (SecurityException e) { return FALLBACK_FILESTORE_ATTRIBUTES; } } private static FileStoreAttributes getFileStoreAttributes(Path dir) { FileStore s; try { if (Files.exists(dir)) { s = Files.getFileStore(dir); FileStoreAttributes c = attributeCache.get(s); if (c != null) { return c; } if (!Files.isWritable(dir)) { // cannot measure resolution in a read-only directory LOG.debug( "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } } else { // cannot determine FileStore of an unborn directory LOG.debug( "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture .supplyAsync(() -> { Lock lock = locks.computeIfAbsent(s, l -> new ReentrantLock()); if (!lock.tryLock()) { LOG.debug( "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$ Thread.currentThread(), dir); return Optional.empty(); } Optional<FileStoreAttributes> attributes = Optional .empty(); try { // Some earlier future might have set the value // and removed itself since we checked for the // value above. Hence check cache again. FileStoreAttributes c = attributeCache .get(s); if (c != null) { return Optional.of(c); } attributes = readFromConfig(s); if (attributes.isPresent()) { attributeCache.put(s, attributes.get()); return attributes; } Optional<Duration> resolution = measureFsTimestampResolution( s, dir); if (resolution.isPresent()) { c = new FileStoreAttributes( resolution.get()); attributeCache.put(s, c); // for high timestamp resolution measure // minimal racy interval if (c.fsTimestampResolution .toNanos() < 100_000_000L) { c.minimalRacyInterval = measureMinimalRacyInterval( dir); } if (LOG.isDebugEnabled()) { LOG.debug(c.toString()); } saveToConfig(s, c); } attributes = Optional.of(c); } finally { lock.unlock(); locks.remove(s); } return attributes; }); f = f.exceptionally(e -> { LOG.error(e.getLocalizedMessage(), e); return Optional.empty(); }); // even if measuring in background wait a little - if the result // arrives, it's better than returning the large fallback Optional<FileStoreAttributes> d = background.get() ? f.get( 100, TimeUnit.MILLISECONDS) : f.get(); if (d.isPresent()) { return d.get(); } // return fallback until measurement is finished } catch (IOException | InterruptedException | ExecutionException | CancellationException e) { LOG.error(e.getMessage(), e); } catch (TimeoutException | SecurityException e) { // use fallback } LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$ Thread.currentThread(), dir); return FALLBACK_FILESTORE_ATTRIBUTES; } @SuppressWarnings("boxing") private static Duration measureMinimalRacyInterval(Path dir) { LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$ Thread.currentThread(), dir); int n = 0; int failures = 0; long racyNanos = 0; ArrayList<Long> deltas = new ArrayList<>(); Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ Instant end = Instant.now().plusSeconds(3); try { Files.createFile(probe); do { n++; write(probe, "a"); //$NON-NLS-1$ FileSnapshot snapshot = FileSnapshot.save(probe.toFile()); read(probe); write(probe, "b"); //$NON-NLS-1$ if (!snapshot.isModified(probe.toFile())) { deltas.add(Long.valueOf(snapshot.lastDelta())); racyNanos = snapshot.lastRacyThreshold(); failures++; } } while (Instant.now().compareTo(end) < 0); } catch (IOException e) { LOG.error(e.getMessage(), e); return FALLBACK_MIN_RACY_INTERVAL; } finally { deleteProbe(probe); } if (failures > 0) { Stats stats = new Stats(); for (Long d : deltas) { stats.add(d); } LOG.debug( "delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$ + "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$ + " delta max [ns], delta avg [ns]," //$NON-NLS-1$ + " delta stddev [ns]\n" //$NON-NLS-1$ + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$ n, failures, racyNanos, stats.min(), stats.max(), stats.avg(), stats.stddev()); return Duration .ofNanos(Double.valueOf(stats.max()).longValue()); } // since no failures occurred using the measured filesystem // timestamp resolution there is no need for minimal racy interval LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$ Thread.currentThread()); return Duration.ZERO; } private static void write(Path p, String body) throws IOException { FileUtils.mkdirs(p.getParent().toFile(), true); try (Writer w = new OutputStreamWriter(Files.newOutputStream(p), UTF_8)) { w.write(body); } } private static String read(Path p) throws IOException { final byte[] body = IO.readFully(p.toFile()); return new String(body, 0, body.length, UTF_8); } private static Optional<Duration> measureFsTimestampResolution( FileStore s, Path dir) { LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$ Thread.currentThread(), s, dir); Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ try { Files.createFile(probe); FileTime t1 = Files.getLastModifiedTime(probe); FileTime t2 = t1; Instant t1i = t1.toInstant(); for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) { Files.setLastModifiedTime(probe, FileTime.from(t1i.plusNanos(i * 1000))); t2 = Files.getLastModifiedTime(probe); } Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant()); Duration clockResolution = measureClockResolution(); fsResolution = fsResolution.plus(clockResolution); LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$ Thread.currentThread(), s, dir); return Optional.of(fsResolution); } catch (AccessDeniedException e) { LOG.warn(e.getLocalizedMessage(), e); // see bug 548648 } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } finally { deleteProbe(probe); } return Optional.empty(); } private static Duration measureClockResolution() { Duration clockResolution = Duration.ZERO; for (int i = 0; i < 10; i++) { Instant t1 = Instant.now(); Instant t2 = t1; while (t2.compareTo(t1) <= 0) { t2 = Instant.now(); } Duration r = Duration.between(t1, t2); if (r.compareTo(clockResolution) > 0) { clockResolution = r; } } return clockResolution; } private static void deleteProbe(Path probe) { try { FileUtils.delete(probe.toFile(), FileUtils.SKIP_MISSING | FileUtils.RETRY); } catch (IOException e) { LOG.error(e.getMessage(), e); } } private static Optional<FileStoreAttributes> readFromConfig( FileStore s) { StoredConfig userConfig; try { userConfig = SystemReader.getInstance().getUserConfig(); } catch (IOException | ConfigInvalidException e) { LOG.error(JGitText.get().readFileStoreAttributesFailed, e); return Optional.empty(); } String key = getConfigKey(s); Duration resolution = Duration.ofNanos(userConfig.getTimeUnit( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS)); if (UNDEFINED_DURATION.equals(resolution)) { return Optional.empty(); } Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD, UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS)); FileStoreAttributes c = new FileStoreAttributes(resolution); if (!UNDEFINED_DURATION.equals(minRacyThreshold)) { c.minimalRacyInterval = minRacyThreshold; } return Optional.of(c); } private static void saveToConfig(FileStore s, FileStoreAttributes c) { StoredConfig userConfig; try { userConfig = SystemReader.getInstance().getUserConfig(); } catch (IOException | ConfigInvalidException e) { LOG.error(JGitText.get().saveFileStoreAttributesFailed, e); return; } long resolution = c.getFsTimestampResolution().toNanos(); TimeUnit resolutionUnit = getUnit(resolution); long resolutionValue = resolutionUnit.convert(resolution, TimeUnit.NANOSECONDS); long minRacyThreshold = c.getMinimalRacyInterval().toNanos(); TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold); long minRacyThresholdValue = minRacyThresholdUnit .convert(minRacyThreshold, TimeUnit.NANOSECONDS); final int max_retries = 5; int retries = 0; boolean succeeded = false; String key = getConfigKey(s); while (!succeeded && retries < max_retries) { try { userConfig.load(); userConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(resolutionValue), resolutionUnit.name().toLowerCase())); userConfig.setString( ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD, String.format("%d %s", //$NON-NLS-1$ Long.valueOf(minRacyThresholdValue), minRacyThresholdUnit.name().toLowerCase())); userConfig.save(); succeeded = true; } catch (LockFailedException e) { // race with another thread, wait a bit and try again try { retries++; if (retries < max_retries) { Thread.sleep(100); LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$ userConfig, Integer.valueOf(retries), Integer.valueOf(max_retries)); } else { LOG.warn(MessageFormat.format( JGitText.get().lockFailedRetry, userConfig, Integer.valueOf(retries))); } } catch (InterruptedException e1) { Thread.currentThread().interrupt(); break; } } catch (IOException e) { LOG.error(MessageFormat.format( JGitText.get().cannotSaveConfig, userConfig), e); break; } catch (ConfigInvalidException e) { LOG.error(MessageFormat.format( JGitText.get().repositoryConfigFileInvalid, userConfig, e.getMessage())); break; } } } private static String getConfigKey(FileStore s) { final String storeKey; if (SystemReader.getInstance().isWindows()) { Object attribute = null; try { attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$ } catch (IOException ignored) { // ignore } if (attribute instanceof Integer) { storeKey = attribute.toString(); } else { storeKey = s.name(); } } else { storeKey = s.name(); } return javaVersionPrefix + storeKey; } private static TimeUnit getUnit(long nanos) { TimeUnit unit; if (nanos < 200_000L) { unit = TimeUnit.NANOSECONDS; } else if (nanos < 200_000_000L) { unit = TimeUnit.MICROSECONDS; } else { unit = TimeUnit.MILLISECONDS; } return unit; } private final @NonNull Duration fsTimestampResolution; private Duration minimalRacyInterval;
Returns:the measured minimal interval after a file has been modified in which we cannot rely on lastModified to detect modifications
/** * @return the measured minimal interval after a file has been modified * in which we cannot rely on lastModified to detect * modifications */
public Duration getMinimalRacyInterval() { return minimalRacyInterval; }
Returns:the measured filesystem timestamp resolution
/** * @return the measured filesystem timestamp resolution */
@NonNull public Duration getFsTimestampResolution() { return fsTimestampResolution; }
Construct a FileStoreAttributeCache entry for the given filesystem timestamp resolution
Params:
  • fsTimestampResolution –
/** * Construct a FileStoreAttributeCache entry for the given filesystem * timestamp resolution * * @param fsTimestampResolution */
public FileStoreAttributes( @NonNull Duration fsTimestampResolution) { this.fsTimestampResolution = fsTimestampResolution; this.minimalRacyInterval = Duration.ZERO; } @SuppressWarnings({ "nls", "boxing" }) @Override public String toString() { return String.format( "FileStoreAttributes[fsTimestampResolution=%,d µs, " + "minimalRacyInterval=%,d µs]", fsTimestampResolution.toNanos() / 1000, minimalRacyInterval.toNanos() / 1000); } }
The auto-detected implementation selected for this operating system and JRE.
/** The auto-detected implementation selected for this operating system and JRE. */
public static final FS DETECTED = detect(); private volatile static FSFactory factory;
Auto-detect the appropriate file system abstraction.
Returns:detected file system abstraction
/** * Auto-detect the appropriate file system abstraction. * * @return detected file system abstraction */
public static FS detect() { return detect(null); }
Whether FileStore attributes should be determined asynchronously
Params:
  • asynch – whether FileStore attributes should be determined asynchronously. If false access to cached attributes may block for some seconds for the first call per FileStore
Since:5.1.9
/** * Whether FileStore attributes should be determined asynchronously * * @param asynch * whether FileStore attributes should be determined * asynchronously. If false access to cached attributes may block * for some seconds for the first call per FileStore * @since 5.1.9 */
public static void setAsyncFileStoreAttributes(boolean asynch) { FileStoreAttributes.setBackground(asynch); }
Auto-detect the appropriate file system abstraction, taking into account the presence of a Cygwin installation on the system. Using jgit in combination with Cygwin requires a more elaborate (and possibly slower) resolution of file system paths.
Params:
  • cygwinUsed –
    • Boolean.TRUE to assume that Cygwin is used in combination with jgit
    • Boolean.FALSE to assume that Cygwin is not used with jgit
    • null to auto-detect whether a Cygwin installation is present on the system and in this case assume that Cygwin is used
    Note: this parameter is only relevant on Windows.
Returns:detected file system abstraction
/** * Auto-detect the appropriate file system abstraction, taking into account * the presence of a Cygwin installation on the system. Using jgit in * combination with Cygwin requires a more elaborate (and possibly slower) * resolution of file system paths. * * @param cygwinUsed * <ul> * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in * combination with jgit</li> * <li><code>Boolean.FALSE</code> to assume that Cygwin is * <b>not</b> used with jgit</li> * <li><code>null</code> to auto-detect whether a Cygwin * installation is present on the system and in this case assume * that Cygwin is used</li> * </ul> * * Note: this parameter is only relevant on Windows. * @return detected file system abstraction */
public static FS detect(Boolean cygwinUsed) { if (factory == null) { factory = new FS.FSFactory(); } return factory.detect(cygwinUsed); }
Get cached FileStore attributes, if not yet available measure them using a probe file under the given directory.
Params:
  • dir – the directory under which the probe file will be created to measure the timer resolution.
Returns:measured filesystem timestamp resolution
Since:5.1.9
/** * Get cached FileStore attributes, if not yet available measure them using * a probe file under the given directory. * * @param dir * the directory under which the probe file will be created to * measure the timer resolution. * @return measured filesystem timestamp resolution * @since 5.1.9 */
public static FileStoreAttributes getFileStoreAttributes( @NonNull Path dir) { return FileStoreAttributes.get(dir); } private volatile Holder<File> userHome; private volatile Holder<File> gitSystemConfig;
Constructs a file system abstraction.
/** * Constructs a file system abstraction. */
protected FS() { // Do nothing by default. }
Initialize this FS using another's current settings.
Params:
  • src – the source FS to copy from.
/** * Initialize this FS using another's current settings. * * @param src * the source FS to copy from. */
protected FS(FS src) { userHome = src.userHome; gitSystemConfig = src.gitSystemConfig; }
Create a new instance of the same type of FS.
Returns:a new instance of the same type of FS.
/** * Create a new instance of the same type of FS. * * @return a new instance of the same type of FS. */
public abstract FS newInstance();
Does this operating system and JRE support the execute flag on files?
Returns:true if this implementation can provide reasonably accurate executable bit information; false otherwise.
/** * Does this operating system and JRE support the execute flag on files? * * @return true if this implementation can provide reasonably accurate * executable bit information; false otherwise. */
public abstract boolean supportsExecute();
Does this file system support atomic file creation via java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is not guaranteed that when two file system clients run createNewFile() in parallel only one will succeed. In such cases both clients may think they created a new file.
Returns:true if this implementation support atomic creation of new Files by File.createNewFile()
Since:4.5
/** * Does this file system support atomic file creation via * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is * not guaranteed that when two file system clients run createNewFile() in * parallel only one will succeed. In such cases both clients may think they * created a new file. * * @return true if this implementation support atomic creation of new Files * by {@link java.io.File#createNewFile()} * @since 4.5 */
public boolean supportsAtomicCreateNewFile() { return true; }
Does this operating system and JRE supports symbolic links. The capability to handle symbolic links is detected at runtime.
Returns:true if symbolic links may be used
Since:3.0
/** * Does this operating system and JRE supports symbolic links. The * capability to handle symbolic links is detected at runtime. * * @return true if symbolic links may be used * @since 3.0 */
public boolean supportsSymlinks() { if (supportSymlinks == null) { detectSymlinkSupport(); } return Boolean.TRUE.equals(supportSymlinks); } private void detectSymlinkSupport() { File tempFile = null; try { tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$ File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$ createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); } catch (IOException | UnsupportedOperationException | SecurityException | InternalError e) { supportSymlinks = Boolean.FALSE; } finally { if (tempFile != null) { try { FileUtils.delete(tempFile); } catch (IOException e) { throw new RuntimeException(e); // panic } } } }
Is this file system case sensitive
Returns:true if this implementation is case sensitive
/** * Is this file system case sensitive * * @return true if this implementation is case sensitive */
public abstract boolean isCaseSensitive();
Determine if the file is executable (or not).

Not all platforms and JREs support executable flags on files. If the feature is unsupported this method will always return false.

If the platform supports symbolic links and f is a symbolic link this method returns false, rather than the state of the executable flags on the target file.

Params:
  • f – abstract path to test.
Returns:true if the file is believed to be executable by the user.
/** * Determine if the file is executable (or not). * <p> * Not all platforms and JREs support executable flags on files. If the * feature is unsupported this method will always return false. * <p> * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link * this method returns false, rather than the state of the executable flags * on the target file.</em> * * @param f * abstract path to test. * @return true if the file is believed to be executable by the user. */
public abstract boolean canExecute(File f);
Set a file to be executable by the user.

Not all platforms and JREs support executable flags on files. If the feature is unsupported this method will always return false and no changes will be made to the file specified.

Params:
  • f – path to modify the executable status of.
  • canExec – true to enable execution; false to disable it.
Returns:true if the change succeeded; false otherwise.
/** * Set a file to be executable by the user. * <p> * Not all platforms and JREs support executable flags on files. If the * feature is unsupported this method will always return false and no * changes will be made to the file specified. * * @param f * path to modify the executable status of. * @param canExec * true to enable execution; false to disable it. * @return true if the change succeeded; false otherwise. */
public abstract boolean setExecute(File f, boolean canExec);
Get the last modified time of a file system object. If the OS/JRE support symbolic links, the modification time of the link is returned, rather than that of the link target.
Params:
  • f – a File object.
Throws:
Returns:last modified time of f
Since:3.0
Deprecated:use lastModifiedInstant(Path) instead
/** * Get the last modified time of a file system object. If the OS/JRE support * symbolic links, the modification time of the link is returned, rather * than that of the link target. * * @param f * a {@link java.io.File} object. * @return last modified time of f * @throws java.io.IOException * @since 3.0 * @deprecated use {@link #lastModifiedInstant(Path)} instead */
@Deprecated public long lastModified(File f) throws IOException { return FileUtils.lastModified(f); }
Get the last modified time of a file system object. If the OS/JRE support symbolic links, the modification time of the link is returned, rather than that of the link target.
Params:
  • p – a Path object.
Returns:last modified time of p
Since:5.1.9
/** * Get the last modified time of a file system object. If the OS/JRE support * symbolic links, the modification time of the link is returned, rather * than that of the link target. * * @param p * a {@link Path} object. * @return last modified time of p * @since 5.1.9 */
public Instant lastModifiedInstant(Path p) { return FileUtils.lastModifiedInstant(p); }
Get the last modified time of a file system object. If the OS/JRE support symbolic links, the modification time of the link is returned, rather than that of the link target.
Params:
  • f – a File object.
Returns:last modified time of p
Since:5.1.9
/** * Get the last modified time of a file system object. If the OS/JRE support * symbolic links, the modification time of the link is returned, rather * than that of the link target. * * @param f * a {@link File} object. * @return last modified time of p * @since 5.1.9 */
public Instant lastModifiedInstant(File f) { return FileUtils.lastModifiedInstant(f.toPath()); }
Set the last modified time of a file system object. If the OS/JRE support symbolic links, the link is modified, not the target,
Params:
  • f – a File object.
  • time – last modified time
Throws:
Since:3.0
Deprecated:use setLastModified(Path, Instant) instead
/** * Set the last modified time of a file system object. If the OS/JRE support * symbolic links, the link is modified, not the target, * * @param f * a {@link java.io.File} object. * @param time * last modified time * @throws java.io.IOException * @since 3.0 * @deprecated use {@link #setLastModified(Path, Instant)} instead */
@Deprecated public void setLastModified(File f, long time) throws IOException { FileUtils.setLastModified(f, time); }
Set the last modified time of a file system object. If the OS/JRE support symbolic links, the link is modified, not the target,
Params:
  • p – a Path object.
  • time – last modified time
Throws:
Since:5.1.9
/** * Set the last modified time of a file system object. If the OS/JRE support * symbolic links, the link is modified, not the target, * * @param p * a {@link Path} object. * @param time * last modified time * @throws java.io.IOException * @since 5.1.9 */
public void setLastModified(Path p, Instant time) throws IOException { FileUtils.setLastModified(p, time); }
Get the length of a file or link, If the OS/JRE supports symbolic links it's the length of the link, else the length of the target.
Params:
  • path – a File object.
Throws:
Returns:length of a file
Since:3.0
/** * Get the length of a file or link, If the OS/JRE supports symbolic links * it's the length of the link, else the length of the target. * * @param path * a {@link java.io.File} object. * @return length of a file * @throws java.io.IOException * @since 3.0 */
public long length(File path) throws IOException { return FileUtils.getLength(path); }
Delete a file. Throws an exception if delete fails.
Params:
  • f – a File object.
Throws:
  • IOException – this may be a Java7 subclass with detailed information
Since:3.3
/** * Delete a file. Throws an exception if delete fails. * * @param f * a {@link java.io.File} object. * @throws java.io.IOException * this may be a Java7 subclass with detailed information * @since 3.3 */
public void delete(File f) throws IOException { FileUtils.delete(f); }
Resolve this file to its actual path name that the JRE can use.

This method can be relatively expensive. Computing a translation may require forking an external process per path name translated. Callers should try to minimize the number of translations necessary by caching the results.

Not all platforms and JREs require path name translation. Currently only Cygwin on Win32 require translation for Cygwin based paths.

Params:
  • dir – directory relative to which the path name is.
  • name – path name to translate.
Returns:the translated path. new File(dir,name) if this platform does not require path name translation.
/** * Resolve this file to its actual path name that the JRE can use. * <p> * This method can be relatively expensive. Computing a translation may * require forking an external process per path name translated. Callers * should try to minimize the number of translations necessary by caching * the results. * <p> * Not all platforms and JREs require path name translation. Currently only * Cygwin on Win32 require translation for Cygwin based paths. * * @param dir * directory relative to which the path name is. * @param name * path name to translate. * @return the translated path. <code>new File(dir,name)</code> if this * platform does not require path name translation. */
public File resolve(File dir, String name) { final File abspn = new File(name); if (abspn.isAbsolute()) return abspn; return new File(dir, name); }
Determine the user's home directory (location where preferences are).

This method can be expensive on the first invocation if path name translation is required. Subsequent invocations return a cached result.

Not all platforms and JREs require path name translation. Currently only Cygwin on Win32 requires translation of the Cygwin HOME directory.

Returns:the user's home directory; null if the user does not have one.
/** * Determine the user's home directory (location where preferences are). * <p> * This method can be expensive on the first invocation if path name * translation is required. Subsequent invocations return a cached result. * <p> * Not all platforms and JREs require path name translation. Currently only * Cygwin on Win32 requires translation of the Cygwin HOME directory. * * @return the user's home directory; null if the user does not have one. */
public File userHome() { Holder<File> p = userHome; if (p == null) { p = new Holder<>(userHomeImpl()); userHome = p; } return p.value; }
Set the user's home directory location.
Params:
  • path – the location of the user's preferences; null if there is no home directory for the current user.
Returns:this.
/** * Set the user's home directory location. * * @param path * the location of the user's preferences; null if there is no * home directory for the current user. * @return {@code this}. */
public FS setUserHome(File path) { userHome = new Holder<>(path); return this; }
Does this file system have problems with atomic renames?
Returns:true if the caller should retry a failed rename of a lock file.
/** * Does this file system have problems with atomic renames? * * @return true if the caller should retry a failed rename of a lock file. */
public abstract boolean retryFailedLockFileCommit();
Return all the attributes of a file, without following symbolic links.
Params:
  • file –
Throws:
  • IOException – in case of any I/O errors accessing the file
Returns:BasicFileAttributes of the file
Since:4.5.6
/** * Return all the attributes of a file, without following symbolic links. * * @param file * @return {@link BasicFileAttributes} of the file * @throws IOException in case of any I/O errors accessing the file * * @since 4.5.6 */
public BasicFileAttributes fileAttributes(File file) throws IOException { return FileUtils.fileAttributes(file); }
Determine the user's home directory (location where preferences are).
Returns:the user's home directory; null if the user does not have one.
/** * Determine the user's home directory (location where preferences are). * * @return the user's home directory; null if the user does not have one. */
protected File userHomeImpl() { final String home = AccessController.doPrivileged( (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$ ); if (home == null || home.length() == 0) return null; return new File(home).getAbsoluteFile(); }
Searches the given path to see if it contains one of the given files. Returns the first it finds. Returns null if not found or if path is null.
Params:
  • path – List of paths to search separated by File.pathSeparator
  • lookFor – Files to search for in the given path
Returns:the first match found, or null
Since:3.0
/** * Searches the given path to see if it contains one of the given files. * Returns the first it finds. Returns null if not found or if path is null. * * @param path * List of paths to search separated by File.pathSeparator * @param lookFor * Files to search for in the given path * @return the first match found, or null * @since 3.0 */
protected static File searchPath(String path, String... lookFor) { if (path == null) return null; for (String p : path.split(File.pathSeparator)) { for (String command : lookFor) { final File file = new File(p, command); try { if (file.isFile()) { return file.getAbsoluteFile(); } } catch (SecurityException e) { LOG.warn(MessageFormat.format( JGitText.get().skipNotAccessiblePath, file.getPath())); } } } return null; }
Execute a command and return a single line of output as a String
Params:
  • dir – Working directory for the command
  • command – as component array
  • encoding – to be used to parse the command's output
Throws:
Returns:the one-line output of the command or null if there is none
/** * Execute a command and return a single line of output as a String * * @param dir * Working directory for the command * @param command * as component array * @param encoding * to be used to parse the command's output * @return the one-line output of the command or {@code null} if there is * none * @throws org.eclipse.jgit.errors.CommandFailedException * thrown when the command failed (return code was non-zero) */
@Nullable protected static String readPipe(File dir, String[] command, String encoding) throws CommandFailedException { return readPipe(dir, command, encoding, null); }
Execute a command and return a single line of output as a String
Params:
  • dir – Working directory for the command
  • command – as component array
  • encoding – to be used to parse the command's output
  • env – Map of environment variables to be merged with those of the current process
Throws:
Returns:the one-line output of the command or null if there is none
Since:4.0
/** * Execute a command and return a single line of output as a String * * @param dir * Working directory for the command * @param command * as component array * @param encoding * to be used to parse the command's output * @param env * Map of environment variables to be merged with those of the * current process * @return the one-line output of the command or {@code null} if there is * none * @throws org.eclipse.jgit.errors.CommandFailedException * thrown when the command failed (return code was non-zero) * @since 4.0 */
@Nullable protected static String readPipe(File dir, String[] command, String encoding, Map<String, String> env) throws CommandFailedException { final boolean debug = LOG.isDebugEnabled(); try { if (debug) { LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$ + dir); } ProcessBuilder pb = new ProcessBuilder(command); pb.directory(dir); if (env != null) { pb.environment().putAll(env); } Process p; try { p = pb.start(); } catch (IOException e) { // Process failed to start throw new CommandFailedException(-1, e.getMessage(), e); } p.getOutputStream().close(); GobblerThread gobbler = new GobblerThread(p, command, dir); gobbler.start(); String r = null; try (BufferedReader lineRead = new BufferedReader( new InputStreamReader(p.getInputStream(), encoding))) { r = lineRead.readLine(); if (debug) { LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$ LOG.debug("remaining output:\n"); //$NON-NLS-1$ String l; while ((l = lineRead.readLine()) != null) { LOG.debug(l); } } } for (;;) { try { int rc = p.waitFor(); gobbler.join(); if (rc == 0 && !gobbler.fail.get()) { return r; } else { if (debug) { LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$ } throw new CommandFailedException(rc, gobbler.errorMessage.get(), gobbler.exception.get()); } } catch (InterruptedException ie) { // Stop bothering me, I have a zombie to reap. } } } catch (IOException e) { LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ } catch (AccessControlException e) { LOG.warn(MessageFormat.format( JGitText.get().readPipeIsNotAllowedRequiredPermission, command, dir, e.getPermission())); } catch (SecurityException e) { LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed, command, dir)); } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ } return null; } private static class GobblerThread extends Thread { /* The process has 5 seconds to exit after closing stderr */ private static final int PROCESS_EXIT_TIMEOUT = 5; private final Process p; private final String desc; private final String dir; final AtomicBoolean fail = new AtomicBoolean(); final AtomicReference<String> errorMessage = new AtomicReference<>(); final AtomicReference<Throwable> exception = new AtomicReference<>(); GobblerThread(Process p, String[] command, File dir) { this.p = p; this.desc = Arrays.toString(command); this.dir = Objects.toString(dir); } @Override public void run() { StringBuilder err = new StringBuilder(); try (InputStream is = p.getErrorStream()) { int ch; while ((ch = is.read()) != -1) { err.append((char) ch); } } catch (IOException e) { if (waitForProcessCompletion(e) && p.exitValue() != 0) { setError(e, e.getMessage(), p.exitValue()); fail.set(true); } else { // ignore. command terminated faster and stream was just closed // or the process didn't terminate within timeout } } finally { if (waitForProcessCompletion(null) && err.length() > 0) { setError(null, err.toString(), p.exitValue()); if (p.exitValue() != 0) { fail.set(true); } } } } @SuppressWarnings("boxing") private boolean waitForProcessCompletion(IOException originalError) { try { if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) { setError(originalError, MessageFormat.format( JGitText.get().commandClosedStderrButDidntExit, desc, PROCESS_EXIT_TIMEOUT), -1); fail.set(true); return false; } } catch (InterruptedException e) { setError(originalError, MessageFormat.format( JGitText.get().threadInterruptedWhileRunning, desc), -1); fail.set(true); return false; } return true; } private void setError(IOException e, String message, int exitCode) { exception.set(e); errorMessage.set(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfCommand, desc, dir, Integer.valueOf(exitCode), message)); } }
Discover the path to the Git executable.
Returns:the path to the Git executable or null if it cannot be determined.
Since:4.0
/** * Discover the path to the Git executable. * * @return the path to the Git executable or {@code null} if it cannot be * determined. * @since 4.0 */
protected abstract File discoverGitExe();
Discover the path to the system-wide Git configuration file
Returns:the path to the system-wide Git configuration file or null if it cannot be determined.
Since:4.0
/** * Discover the path to the system-wide Git configuration file * * @return the path to the system-wide Git configuration file or * {@code null} if it cannot be determined. * @since 4.0 */
protected File discoverGitSystemConfig() { File gitExe = discoverGitExe(); if (gitExe == null) { return null; } // Bug 480782: Check if the discovered git executable is JGit CLI String v; try { v = readPipe(gitExe.getParentFile(), new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$ Charset.defaultCharset().name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (StringUtils.isEmptyOrNull(v) || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$ return null; } // Trick Git into printing the path to the config file by using "echo" // as the editor. Map<String, String> env = new HashMap<>(); env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$ String w; try { w = readPipe(gitExe.getParentFile(), new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name(), env); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); return null; } if (StringUtils.isEmptyOrNull(w)) { return null; } return new File(w); }
Get the currently used path to the system-wide Git configuration file.
Returns:the currently used path to the system-wide Git configuration file or null if none has been set.
Since:4.0
/** * Get the currently used path to the system-wide Git configuration file. * * @return the currently used path to the system-wide Git configuration file * or {@code null} if none has been set. * @since 4.0 */
public File getGitSystemConfig() { if (gitSystemConfig == null) { gitSystemConfig = new Holder<>(discoverGitSystemConfig()); } return gitSystemConfig.value; }
Set the path to the system-wide Git configuration file to use.
Params:
  • configFile – the path to the config file.
Returns:this
Since:4.0
/** * Set the path to the system-wide Git configuration file to use. * * @param configFile * the path to the config file. * @return {@code this} * @since 4.0 */
public FS setGitSystemConfig(File configFile) { gitSystemConfig = new Holder<>(configFile); return this; }
Get the parent directory of this file's parent directory
Params:
  • grandchild – a File object.
Returns:the parent directory of this file's parent directory or null in case there's no grandparent directory
Since:4.0
/** * Get the parent directory of this file's parent directory * * @param grandchild * a {@link java.io.File} object. * @return the parent directory of this file's parent directory or * {@code null} in case there's no grandparent directory * @since 4.0 */
protected static File resolveGrandparentFile(File grandchild) { if (grandchild != null) { File parent = grandchild.getParentFile(); if (parent != null) return parent.getParentFile(); } return null; }
Check if a file is a symbolic link and read it
Params:
  • path – a File object.
Throws:
Returns:target of link or null
Since:3.0
/** * Check if a file is a symbolic link and read it * * @param path * a {@link java.io.File} object. * @return target of link or null * @throws java.io.IOException * @since 3.0 */
public String readSymLink(File path) throws IOException { return FileUtils.readSymLink(path); }
Whether the path is a symbolic link (and we support these).
Params:
  • path – a File object.
Throws:
Returns:true if the path is a symbolic link (and we support these)
Since:3.0
/** * Whether the path is a symbolic link (and we support these). * * @param path * a {@link java.io.File} object. * @return true if the path is a symbolic link (and we support these) * @throws java.io.IOException * @since 3.0 */
public boolean isSymLink(File path) throws IOException { return FileUtils.isSymlink(path); }
Tests if the path exists, in case of a symbolic link, true even if the target does not exist
Params:
  • path – a File object.
Returns:true if path exists
Since:3.0
/** * Tests if the path exists, in case of a symbolic link, true even if the * target does not exist * * @param path * a {@link java.io.File} object. * @return true if path exists * @since 3.0 */
public boolean exists(File path) { return FileUtils.exists(path); }
Check if path is a directory. If the OS/JRE supports symbolic links and path is a symbolic link to a directory, this method returns false.
Params:
  • path – a File object.
Returns:true if file is a directory,
Since:3.0
/** * Check if path is a directory. If the OS/JRE supports symbolic links and * path is a symbolic link to a directory, this method returns false. * * @param path * a {@link java.io.File} object. * @return true if file is a directory, * @since 3.0 */
public boolean isDirectory(File path) { return FileUtils.isDirectory(path); }
Examine if path represents a regular file. If the OS/JRE supports symbolic links the test returns false if path represents a symbolic link.
Params:
  • path – a File object.
Returns:true if path represents a regular file
Since:3.0
/** * Examine if path represents a regular file. If the OS/JRE supports * symbolic links the test returns false if path represents a symbolic link. * * @param path * a {@link java.io.File} object. * @return true if path represents a regular file * @since 3.0 */
public boolean isFile(File path) { return FileUtils.isFile(path); }
Whether path is hidden, either starts with . on unix or has the hidden attribute in windows
Params:
  • path – a File object.
Throws:
Returns:true if path is hidden, either starts with . on unix or has the hidden attribute in windows
Since:3.0
/** * Whether path is hidden, either starts with . on unix or has the hidden * attribute in windows * * @param path * a {@link java.io.File} object. * @return true if path is hidden, either starts with . on unix or has the * hidden attribute in windows * @throws java.io.IOException * @since 3.0 */
public boolean isHidden(File path) throws IOException { return FileUtils.isHidden(path); }
Set the hidden attribute for file whose name starts with a period.
Params:
  • path – a File object.
  • hidden – whether to set the file hidden
Throws:
Since:3.0
/** * Set the hidden attribute for file whose name starts with a period. * * @param path * a {@link java.io.File} object. * @param hidden * whether to set the file hidden * @throws java.io.IOException * @since 3.0 */
public void setHidden(File path, boolean hidden) throws IOException { FileUtils.setHidden(path, hidden); }
Create a symbolic link
Params:
  • path – a File object.
  • target – target path of the symlink
Throws:
Since:3.0
/** * Create a symbolic link * * @param path * a {@link java.io.File} object. * @param target * target path of the symlink * @throws java.io.IOException * @since 3.0 */
public void createSymLink(File path, String target) throws IOException { FileUtils.createSymLink(path, target); }
Create a new file. See File.createNewFile(). Subclasses of this class may take care to provide a safe implementation for this even if supportsAtomicCreateNewFile() is false
Params:
  • path – the file to be created
Throws:
Returns:true if the file was created, false if the file already existed
Deprecated:use createNewFileAtomic(File) instead
Since:4.5
/** * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses * of this class may take care to provide a safe implementation for this * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code> * * @param path * the file to be created * @return <code>true</code> if the file was created, <code>false</code> if * the file already existed * @throws java.io.IOException * @deprecated use {@link #createNewFileAtomic(File)} instead * @since 4.5 */
@Deprecated public boolean createNewFile(File path) throws IOException { return path.createNewFile(); }
A token representing a file created by FS.createNewFileAtomic(File). The token must be retained until the file has been deleted in order to guarantee that the unique file was created atomically. As soon as the file is no longer needed the lock token must be closed.
Since:4.7
/** * A token representing a file created by * {@link #createNewFileAtomic(File)}. The token must be retained until the * file has been deleted in order to guarantee that the unique file was * created atomically. As soon as the file is no longer needed the lock * token must be closed. * * @since 4.7 */
public static class LockToken implements Closeable { private boolean isCreated; private Optional<Path> link; LockToken(boolean isCreated, Optional<Path> link) { this.isCreated = isCreated; this.link = link; }
Returns:true if the file was created successfully
/** * @return {@code true} if the file was created successfully */
public boolean isCreated() { return isCreated; } @Override public void close() { if (!link.isPresent()) { return; } Path p = link.get(); if (!Files.exists(p)) { return; } try { Files.delete(p); } catch (IOException e) { LOG.error(MessageFormat .format(JGitText.get().closeLockTokenFailed, this), e); } } @Override public String toString() { return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$ ", link=" //$NON-NLS-1$ + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$ : "<null>]"); //$NON-NLS-1$ } }
Create a new file. See File.createNewFile(). Subclasses of this class may take care to provide a safe implementation for this even if supportsAtomicCreateNewFile() is false
Params:
  • path – the file to be created
Throws:
Returns:LockToken this token must be closed after the created file was deleted
Since:4.7
/** * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses * of this class may take care to provide a safe implementation for this * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code> * * @param path * the file to be created * @return LockToken this token must be closed after the created file was * deleted * @throws IOException * @since 4.7 */
public LockToken createNewFileAtomic(File path) throws IOException { return new LockToken(path.createNewFile(), Optional.empty()); }
Params:
  • base – The path against which other should be relativized.
  • other – The path that will be made relative to base.
See Also:
Returns:A relative path that, when resolved against base, will yield the original other.
Since:3.7
/** * See * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * The path against which <code>other</code> should be * relativized. * @param other * The path that will be made relative to <code>base</code>. * @return A relative path that, when resolved against <code>base</code>, * will yield the original <code>other</code>. * @see FileUtils#relativizePath(String, String, String, boolean) * @since 3.7 */
public String relativize(String base, String other) { return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive()); }
Enumerates children of a directory.
Params:
  • directory – to get the children of
  • fileModeStrategy – to use to calculate the git mode of a child
Returns:an array of entries for the children
Since:5.0
/** * Enumerates children of a directory. * * @param directory * to get the children of * @param fileModeStrategy * to use to calculate the git mode of a child * @return an array of entries for the children * * @since 5.0 */
public Entry[] list(File directory, FileModeStrategy fileModeStrategy) { final File[] all = directory.listFiles(); if (all == null) { return NO_ENTRIES; } final Entry[] result = new Entry[all.length]; for (int i = 0; i < result.length; i++) { result[i] = new FileEntry(all[i], this, fileModeStrategy); } return result; }
Checks whether the given hook is defined for the given repository, then runs it with the given arguments.

The hook's standard output and error streams will be redirected to System.out and System.err respectively. The hook will have no stdin.

Params:
  • repository – The repository for which a hook should be run.
  • hookName – The name of the hook to be executed.
  • args – Arguments to pass to this hook. Cannot be null, but can be an empty array.
Throws:
  • JGitInternalException – if we fail to run the hook somehow. Causes may include an interrupted process or I/O errors.
Returns:The ProcessResult describing this hook's execution.
Since:4.0
/** * Checks whether the given hook is defined for the given repository, then * runs it with the given arguments. * <p> * The hook's standard output and error streams will be redirected to * <code>System.out</code> and <code>System.err</code> respectively. The * hook will have no stdin. * </p> * * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be <code>null</code>, * but can be an empty array. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 */
public ProcessResult runHookIfPresent(Repository repository, final String hookName, String[] args) throws JGitInternalException { return runHookIfPresent(repository, hookName, args, System.out, System.err, null); }
Checks whether the given hook is defined for the given repository, then runs it with the given arguments.
Params:
  • repository – The repository for which a hook should be run.
  • hookName – The name of the hook to be executed.
  • args – Arguments to pass to this hook. Cannot be null, but can be an empty array.
  • outRedirect – A print stream on which to redirect the hook's stdout. Can be null, in which case the hook's standard output will be lost.
  • errRedirect – A print stream on which to redirect the hook's stderr. Can be null, in which case the hook's standard error will be lost.
  • stdinArgs – A string to pass on to the standard input of the hook. May be null.
Throws:
  • JGitInternalException – if we fail to run the hook somehow. Causes may include an interrupted process or I/O errors.
Returns:The ProcessResult describing this hook's execution.
Since:4.0
/** * Checks whether the given hook is defined for the given repository, then * runs it with the given arguments. * * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be <code>null</code>, * but can be an empty array. * @param outRedirect * A print stream on which to redirect the hook's stdout. Can be * <code>null</code>, in which case the hook's standard output * will be lost. * @param errRedirect * A print stream on which to redirect the hook's stderr. Can be * <code>null</code>, in which case the hook's standard error * will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. May be * <code>null</code>. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 */
public ProcessResult runHookIfPresent(Repository repository, final String hookName, String[] args, PrintStream outRedirect, PrintStream errRedirect, String stdinArgs) throws JGitInternalException { return new ProcessResult(Status.NOT_SUPPORTED); }
See runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String) . Should only be called by FS supporting shell scripts execution.
Params:
  • repository – The repository for which a hook should be run.
  • hookName – The name of the hook to be executed.
  • args – Arguments to pass to this hook. Cannot be null, but can be an empty array.
  • outRedirect – A print stream on which to redirect the hook's stdout. Can be null, in which case the hook's standard output will be lost.
  • errRedirect – A print stream on which to redirect the hook's stderr. Can be null, in which case the hook's standard error will be lost.
  • stdinArgs – A string to pass on to the standard input of the hook. May be null.
Throws:
  • JGitInternalException – if we fail to run the hook somehow. Causes may include an interrupted process or I/O errors.
Returns:The ProcessResult describing this hook's execution.
Since:4.0
/** * See * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)} * . Should only be called by FS supporting shell scripts execution. * * @param repository * The repository for which a hook should be run. * @param hookName * The name of the hook to be executed. * @param args * Arguments to pass to this hook. Cannot be <code>null</code>, * but can be an empty array. * @param outRedirect * A print stream on which to redirect the hook's stdout. Can be * <code>null</code>, in which case the hook's standard output * will be lost. * @param errRedirect * A print stream on which to redirect the hook's stderr. Can be * <code>null</code>, in which case the hook's standard error * will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. May be * <code>null</code>. * @return The ProcessResult describing this hook's execution. * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. * @since 4.0 */
protected ProcessResult internalRunHookIfPresent(Repository repository, final String hookName, String[] args, PrintStream outRedirect, PrintStream errRedirect, String stdinArgs) throws JGitInternalException { final File hookFile = findHook(repository, hookName); if (hookFile == null) return new ProcessResult(Status.NOT_PRESENT); final String hookPath = hookFile.getAbsolutePath(); final File runDirectory; if (repository.isBare()) runDirectory = repository.getDirectory(); else runDirectory = repository.getWorkTree(); final String cmd = relativize(runDirectory.getAbsolutePath(), hookPath); ProcessBuilder hookProcess = runInShell(cmd, args); hookProcess.directory(runDirectory); Map<String, String> environment = hookProcess.environment(); environment.put(Constants.GIT_DIR_KEY, repository.getDirectory().getAbsolutePath()); if (!repository.isBare()) { environment.put(Constants.GIT_WORK_TREE_KEY, repository.getWorkTree().getAbsolutePath()); } try { return new ProcessResult(runProcess(hookProcess, outRedirect, errRedirect, stdinArgs), Status.OK); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfHook, hookName), e); } catch (InterruptedException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionHookExecutionInterrupted, hookName), e); } }
Tries to find a hook matching the given one in the given repository.
Params:
  • repository – The repository within which to find a hook.
  • hookName – The name of the hook we're trying to find.
Returns:The File containing this particular hook if it exists in the given repository, null otherwise.
Since:4.0
/** * Tries to find a hook matching the given one in the given repository. * * @param repository * The repository within which to find a hook. * @param hookName * The name of the hook we're trying to find. * @return The {@link java.io.File} containing this particular hook if it * exists in the given repository, <code>null</code> otherwise. * @since 4.0 */
public File findHook(Repository repository, String hookName) { File gitDir = repository.getDirectory(); if (gitDir == null) return null; final File hookFile = new File(new File(gitDir, Constants.HOOKS), hookName); return hookFile.isFile() ? hookFile : null; }
Runs the given process until termination, clearing its stdout and stderr streams on-the-fly.
Params:
  • processBuilder – The process builder configured for this process.
  • outRedirect – A OutputStream on which to redirect the processes stdout. Can be null, in which case the processes standard output will be lost.
  • errRedirect – A OutputStream on which to redirect the processes stderr. Can be null, in which case the processes standard error will be lost.
  • stdinArgs – A string to pass on to the standard input of the hook. Can be null.
Throws:
  • IOException – if an I/O error occurs while executing this process.
  • InterruptedException – if the current thread is interrupted while waiting for the process to end.
Returns:the exit value of this process.
Since:4.2
/** * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * * @param processBuilder * The process builder configured for this process. * @param outRedirect * A OutputStream on which to redirect the processes stdout. Can * be <code>null</code>, in which case the processes standard * output will be lost. * @param errRedirect * A OutputStream on which to redirect the processes stderr. Can * be <code>null</code>, in which case the processes standard * error will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. Can be * <code>null</code>. * @return the exit value of this process. * @throws java.io.IOException * if an I/O error occurs while executing this process. * @throws java.lang.InterruptedException * if the current thread is interrupted while waiting for the * process to end. * @since 4.2 */
public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws IOException, InterruptedException { InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream( stdinArgs.getBytes(UTF_8)); return runProcess(processBuilder, outRedirect, errRedirect, in); }
Runs the given process until termination, clearing its stdout and stderr streams on-the-fly.
Params:
  • processBuilder – The process builder configured for this process.
  • outRedirect – An OutputStream on which to redirect the processes stdout. Can be null, in which case the processes standard output will be lost.
  • errRedirect – An OutputStream on which to redirect the processes stderr. Can be null, in which case the processes standard error will be lost.
  • inRedirect – An InputStream from which to redirect the processes stdin. Can be null, in which case the process doesn't get any data over stdin. It is assumed that the whole InputStream will be consumed by the process. The method will close the inputstream after all bytes are read.
Throws:
  • IOException – if an I/O error occurs while executing this process.
  • InterruptedException – if the current thread is interrupted while waiting for the process to end.
Returns:the return code of this process.
Since:4.2
/** * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * * @param processBuilder * The process builder configured for this process. * @param outRedirect * An OutputStream on which to redirect the processes stdout. Can * be <code>null</code>, in which case the processes standard * output will be lost. * @param errRedirect * An OutputStream on which to redirect the processes stderr. Can * be <code>null</code>, in which case the processes standard * error will be lost. * @param inRedirect * An InputStream from which to redirect the processes stdin. Can * be <code>null</code>, in which case the process doesn't get * any data over stdin. It is assumed that the whole InputStream * will be consumed by the process. The method will close the * inputstream after all bytes are read. * @return the return code of this process. * @throws java.io.IOException * if an I/O error occurs while executing this process. * @throws java.lang.InterruptedException * if the current thread is interrupted while waiting for the * process to end. * @since 4.2 */
public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, InputStream inRedirect) throws IOException, InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(2); Process process = null; // We'll record the first I/O exception that occurs, but keep on trying // to dispose of our open streams and file handles IOException ioException = null; try { process = processBuilder.start(); executor.execute( new StreamGobbler(process.getErrorStream(), errRedirect)); executor.execute( new StreamGobbler(process.getInputStream(), outRedirect)); @SuppressWarnings("resource") // Closed in the finally block OutputStream outputStream = process.getOutputStream(); try { if (inRedirect != null) { new StreamGobbler(inRedirect, outputStream).copy(); } } finally { try { outputStream.close(); } catch (IOException e) { // When the process exits before consuming the input, the OutputStream // is replaced with the null output stream. This null output stream // throws IOException for all write calls. When StreamGobbler fails to // flush the buffer because of this, this close call tries to flush it // again. This causes another IOException. Since we ignore the // IOException in StreamGobbler, we also ignore the exception here. } } return process.waitFor(); } catch (IOException e) { ioException = e; } finally { shutdownAndAwaitTermination(executor); if (process != null) { try { process.waitFor(); } catch (InterruptedException e) { // Thrown by the outer try. // Swallow this one to carry on our cleanup, and clear the // interrupted flag (processes throw the exception without // clearing the flag). Thread.interrupted(); } // A process doesn't clean its own resources even when destroyed // Explicitly try and close all three streams, preserving the // outer I/O exception if any. if (inRedirect != null) { inRedirect.close(); } try { process.getErrorStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } try { process.getInputStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } try { process.getOutputStream().close(); } catch (IOException e) { ioException = ioException != null ? ioException : e; } process.destroy(); } } // We can only be here if the outer try threw an IOException. throw ioException; }
Shuts down an ExecutorService in two phases, first by calling shutdown to reject incoming tasks, and then calling shutdownNow, if necessary, to cancel any lingering tasks. Returns true if the pool has been properly shutdown, false otherwise.

Params:
  • pool – the pool to shutdown
Returns:true if the pool has been properly shutdown, false otherwise.
/** * Shuts down an {@link ExecutorService} in two phases, first by calling * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if * necessary, to cancel any lingering tasks. Returns true if the pool has * been properly shutdown, false otherwise. * <p> * * @param pool * the pool to shutdown * @return <code>true</code> if the pool has been properly shutdown, * <code>false</code> otherwise. */
private static boolean shutdownAndAwaitTermination(ExecutorService pool) { boolean hasShutdown = true; pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being canceled if (!pool.awaitTermination(60, TimeUnit.SECONDS)) hasShutdown = false; } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); hasShutdown = false; } return hasShutdown; }
Initialize a ProcessBuilder to run a command using the system shell.
Params:
  • cmd – command to execute. This string should originate from the end-user, and thus is platform specific.
  • args – arguments to pass to command. These should be protected from shell evaluation.
Returns:a partially completed process builder. Caller should finish populating directory, environment, and then start the process.
/** * Initialize a ProcessBuilder to run a command using the system shell. * * @param cmd * command to execute. This string should originate from the * end-user, and thus is platform specific. * @param args * arguments to pass to command. These should be protected from * shell evaluation. * @return a partially completed process builder. Caller should finish * populating directory, environment, and then start the process. */
public abstract ProcessBuilder runInShell(String cmd, String[] args);
Execute a command defined by a ProcessBuilder.
Params:
  • pb – The command to be executed
  • in – The standard input stream passed to the process
Throws:
Returns:The result of the executed command
Since:4.2
/** * Execute a command defined by a {@link java.lang.ProcessBuilder}. * * @param pb * The command to be executed * @param in * The standard input stream passed to the process * @return The result of the executed command * @throws java.lang.InterruptedException * @throws java.io.IOException * @since 4.2 */
public ExecutionResult execute(ProcessBuilder pb, InputStream in) throws IOException, InterruptedException { try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null); TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024)) { int rc = runProcess(pb, stdout, stderr, in); return new ExecutionResult(stdout, stderr, rc); } } private static class Holder<V> { final V value; Holder(V value) { this.value = value; } }
File attributes we typically care for.
Since:3.3
/** * File attributes we typically care for. * * @since 3.3 */
public static class Attributes {
Returns:true if this are the attributes of a directory
/** * @return true if this are the attributes of a directory */
public boolean isDirectory() { return isDirectory; }
Returns:true if this are the attributes of an executable file
/** * @return true if this are the attributes of an executable file */
public boolean isExecutable() { return isExecutable; }
Returns:true if this are the attributes of a symbolic link
/** * @return true if this are the attributes of a symbolic link */
public boolean isSymbolicLink() { return isSymbolicLink; }
Returns:true if this are the attributes of a regular file
/** * @return true if this are the attributes of a regular file */
public boolean isRegularFile() { return isRegularFile; }
Returns:the time when the file was created
/** * @return the time when the file was created */
public long getCreationTime() { return creationTime; }
Returns:the time (milliseconds since 1970-01-01) when this object was last modified
Deprecated:use getLastModifiedInstant instead
/** * @return the time (milliseconds since 1970-01-01) when this object was * last modified * @deprecated use getLastModifiedInstant instead */
@Deprecated public long getLastModifiedTime() { return lastModifiedInstant.toEpochMilli(); }
Returns:the time when this object was last modified
Since:5.1.9
/** * @return the time when this object was last modified * @since 5.1.9 */
public Instant getLastModifiedInstant() { return lastModifiedInstant; } private final boolean isDirectory; private final boolean isSymbolicLink; private final boolean isRegularFile; private final long creationTime; private final Instant lastModifiedInstant; private final boolean isExecutable; private final File file; private final boolean exists;
file length
/** * file length */
protected long length = -1; final FS fs; Attributes(FS fs, File file, boolean exists, boolean isDirectory, boolean isExecutable, boolean isSymbolicLink, boolean isRegularFile, long creationTime, Instant lastModifiedInstant, long length) { this.fs = fs; this.file = file; this.exists = exists; this.isDirectory = isDirectory; this.isExecutable = isExecutable; this.isSymbolicLink = isSymbolicLink; this.isRegularFile = isRegularFile; this.creationTime = creationTime; this.lastModifiedInstant = lastModifiedInstant; this.length = length; }
Constructor when there are issues with reading. All attributes except given will be set to the default values.
Params:
  • fs –
  • path –
/** * Constructor when there are issues with reading. All attributes except * given will be set to the default values. * * @param fs * @param path */
public Attributes(File path, FS fs) { this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L); }
Returns:length of this file object
/** * @return length of this file object */
public long getLength() { if (length == -1) return length = file.length(); return length; }
Returns:the filename
/** * @return the filename */
public String getName() { return file.getName(); }
Returns:the file the attributes apply to
/** * @return the file the attributes apply to */
public File getFile() { return file; } boolean exists() { return exists; } }
Get the file attributes we care for.
Params:
  • path – a File object.
Returns:the file attributes we care for.
Since:3.3
/** * Get the file attributes we care for. * * @param path * a {@link java.io.File} object. * @return the file attributes we care for. * @since 3.3 */
public Attributes getAttributes(File path) { boolean isDirectory = isDirectory(path); boolean isFile = !isDirectory && path.isFile(); assert path.exists() == isDirectory || isFile; boolean exists = isDirectory || isFile; boolean canExecute = exists && !isDirectory && canExecute(path); boolean isSymlink = false; Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH; long createTime = 0L; return new Attributes(this, path, exists, isDirectory, canExecute, isSymlink, isFile, createTime, lastModified, -1); }
Normalize the unicode path to composed form.
Params:
  • file – a File object.
Returns:NFC-format File
Since:3.3
/** * Normalize the unicode path to composed form. * * @param file * a {@link java.io.File} object. * @return NFC-format File * @since 3.3 */
public File normalize(File file) { return file; }
Normalize the unicode path to composed form.
Params:
  • name – path name
Returns:NFC-format string
Since:3.3
/** * Normalize the unicode path to composed form. * * @param name * path name * @return NFC-format string * @since 3.3 */
public String normalize(String name) { return name; }
This runnable will consume an input stream's content into an output stream as soon as it gets available.

Typically used to empty processes' standard output and error, preventing them to choke.

Note that a StreamGobbler will never close either of its streams.

/** * This runnable will consume an input stream's content into an output * stream as soon as it gets available. * <p> * Typically used to empty processes' standard output and error, preventing * them to choke. * </p> * <p> * <b>Note</b> that a {@link StreamGobbler} will never close either of its * streams. * </p> */
private static class StreamGobbler implements Runnable { private InputStream in; private OutputStream out; public StreamGobbler(InputStream stream, OutputStream output) { this.in = stream; this.out = output; } @Override public void run() { try { copy(); } catch (IOException e) { // Do nothing on read failure; leave streams open. } } void copy() throws IOException { boolean writeFailure = false; byte buffer[] = new byte[4096]; int readBytes; while ((readBytes = in.read(buffer)) != -1) { // Do not try to write again after a failure, but keep // reading as long as possible to prevent the input stream // from choking. if (!writeFailure && out != null) { try { out.write(buffer, 0, readBytes); out.flush(); } catch (IOException e) { writeFailure = true; } } } } } }