From 58156584fa98e03cb9201b8f44739519f4e6f15e Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 27 Mar 2024 21:36:37 -0700 Subject: [PATCH] Use org.openrewrite.tools jgit. Get rid of vestigial slf4j usage in rewrite-core --- rewrite-benchmarks/build.gradle.kts | 1 - .../java/JavaCompilationUnitState.java | 2 +- rewrite-core/build.gradle.kts | 26 +- .../main/java/org/eclipse/jgit/util/FS.java | 2541 ----------------- .../src/main/java/org/openrewrite/Recipe.java | 6 +- .../src/main/java/org/openrewrite/Result.java | 2 +- .../config/ClasspathScanningLoader.java | 15 +- .../config/YamlResourceLoader.java | 17 +- .../internal/InMemoryDiffEntry.java | 12 +- .../org/openrewrite/marker/GitProvenance.java | 14 +- .../internal/InMemoryDiffEntryTest.java | 2 +- .../openrewrite/marker/GitProvenanceTest.java | 16 +- .../openrewrite/quark/QuarkParserTest.java | 4 +- rewrite-java-test/build.gradle.kts | 1 - rewrite-test/build.gradle.kts | 1 - 15 files changed, 39 insertions(+), 2621 deletions(-) delete mode 100644 rewrite-core/src/main/java/org/eclipse/jgit/util/FS.java diff --git a/rewrite-benchmarks/build.gradle.kts b/rewrite-benchmarks/build.gradle.kts index 195fa0a4688..1ff6d3d8dd9 100644 --- a/rewrite-benchmarks/build.gradle.kts +++ b/rewrite-benchmarks/build.gradle.kts @@ -4,7 +4,6 @@ plugins { } dependencies { - jmh("org.eclipse.jgit:org.eclipse.jgit:5.13.+") jmh("com.google.code.findbugs:jsr305:latest.release") jmh("org.projectlombok:lombok:latest.release") diff --git a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaCompilationUnitState.java b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaCompilationUnitState.java index 9f206d68b74..99ac500afe3 100644 --- a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaCompilationUnitState.java +++ b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaCompilationUnitState.java @@ -71,7 +71,7 @@ public void setup() throws URISyntaxException { sourceFiles = JavaParser.fromJavaVersion() .classpath("jsr305", "classgraph", "jackson-annotations", "micrometer-core", "slf4j-api", - "org.eclipse.jgit") + "org.openrewrite.jgit") // .logCompilationWarningsAndErrors(true) .build() .parse(inputs, null, new InMemoryExecutionContext(Throwable::printStackTrace)) diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 1eebac8d015..35e3c061625 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -1,18 +1,11 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { id("org.openrewrite.build.language-library") - id("org.openrewrite.build.shadow") } dependencies { - // Pin to 5.x for Java 8, as 6.x requires Java 11 - compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") - + api("org.openrewrite.tools:jgit:latest.release") implementation("org.openrewrite.tools:java-object-diff:latest.release") - implementation("io.quarkus.gizmo:gizmo:1.0.+") - api("com.fasterxml.jackson.core:jackson-databind") api("com.fasterxml.jackson.dataformat:jackson-dataformat-smile") api("com.fasterxml.jackson.module:jackson-module-parameter-names") @@ -30,21 +23,4 @@ dependencies { implementation("org.yaml:snakeyaml:latest.release") testImplementation(project(":rewrite-test")) - testImplementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") -} - -val shadowJar = tasks.named("shadowJar") { - dependencies { - include(dependency("org.eclipse.jgit:")) - } - relocate("org.eclipse.jgit", "org.openrewrite.shaded.jgit") - metaInf { - from("$rootDir/LICENSE") - from("$rootDir/NOTICE") - } -} - -tasks.named("test").configure { - dependsOn(shadowJar) - classpath = files(shadowJar, sourceSets.test.get().output, configurations.testRuntimeClasspath) } diff --git a/rewrite-core/src/main/java/org/eclipse/jgit/util/FS.java b/rewrite-core/src/main/java/org/eclipse/jgit/util/FS.java deleted file mode 100644 index 302cda67a93..00000000000 --- a/rewrite-core/src/main/java/org/eclipse/jgit/util/FS.java +++ /dev/null @@ -1,2541 +0,0 @@ -/* - * Copyright (C) 2008, 2020 Shawn O. Pearce and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -package org.eclipse.jgit.util; - -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.*; -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; - -import java.io.*; -import java.nio.charset.Charset; -import java.nio.file.*; -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.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.Instant.EPOCH; - -/** - * !!!THIS SOURCE FILE HAS BEEN MODIFIED!!! - * - * 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 - * {@link #list(File, FileModeStrategy)}. - * - * @since 5.0 - */ - protected static final Entry[] NO_ENTRIES = {}; - - private static final Pattern VERSION = Pattern - .compile("\\s(\\d+)\\.(\\d+)\\.(\\d+)"); //$NON-NLS-1$ - - private volatile Boolean supportSymlinks; - - /** - * 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 - */ - protected FSFactory() { - // empty - } - - /** - * 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(); - } - return new FS_Win32(); - } - return new FS_POSIX(); - } - } - - /** - * 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; - - /** - * @param stdout - * @param stderr - * @param rc - */ - public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, - int rc) { - this.stdout = stdout; - this.stderr = stderr; - this.rc = rc; - } - - /** - * @return buffered standard output stream - */ - public TemporaryBuffer getStdout() { - return stdout; - } - - /** - * @return buffered standard error stream - */ - public TemporaryBuffer getStderr() { - return stderr; - } - - /** - * @return the return code of the process - */ - public int getRc() { - return rc; - } - } - - /** - * Attributes of FileStores on this system - * - * @since 5.1.9 - */ - public static final class FileStoreAttributes { - - /** - * Marker to detect undefined values when reading from the config file. - */ - 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. - *

- * Must be at least 1 second. - *

- */ - 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. - */ - public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes( - FALLBACK_TIMESTAMP_RESOLUTION); - - private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS - .toNanos(1); - - private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS - .toNanos(1); - - private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1); - - /** - * Minimum file system timestamp resolution granularity to check, in - * nanoseconds. Should be a positive power of ten smaller than - * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e., - * minimum value is 1 nanosecond. - *

- * Currently set to 1 microsecond, but could also be lower still. - *

- */ - private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND; - - private static final String JAVA_VERSION_PREFIX = 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); - - private static final Map attributeCache = new ConcurrentHashMap<>(); - - private static final SimpleLruCache attrCacheByPath = new SimpleLruCache<>( - 100, 0.2f); - - private static final AtomicBoolean background = new AtomicBoolean(); - - private static final Map locks = new ConcurrentHashMap<>(); - - private static final AtomicInteger threadNumber = new AtomicInteger(1); - - /** - * Don't use the default thread factory of the ForkJoinPool for the - * CompletableFuture; it runs without any privileges, which causes - * trouble if a SecurityManager is present. - *

- * Instead use normal daemon threads. They'll belong to the - * SecurityManager's thread group, or use the one of the calling thread, - * as appropriate. - *

- * - * @see java.util.concurrent.Executors#newCachedThreadPool() - */ - private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor( - 0, 5, 30L, TimeUnit.SECONDS, - new LinkedBlockingQueue(), - runnable -> { - Thread t = new Thread(runnable, - "JGit-FileStoreAttributeReader-" //$NON-NLS-1$ - + threadNumber.getAndIncrement()); - // Make sure these threads don't prevent application/JVM - // shutdown. - t.setDaemon(true); - return t; - }); - - /** - * Use a separate executor with at most one thread to synchronize - * writing to the config. We write asynchronously since the config - * itself might be on a different file system, which might otherwise - * lead to locking problems. - *

- * Writing the config must not use a daemon thread, otherwise we may - * leave an inconsistent state on disk when the JVM shuts down. Use a - * small keep-alive time to avoid delays on shut-down. - *

- */ - private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor( - 0, 1, 1L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - runnable -> { - Thread t = new Thread(runnable, - "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$ - + threadNumber.getAndIncrement()); - // Make sure these threads do finish - t.setDaemon(false); - return t; - }); - -// Exclude shutdown hook to prevent ClassLoader leaks in the form of shadow references -// static { -// // Shut down the SAVE_RUNNER on System.exit() -// Runtime.getRuntime().addShutdownHook(new Thread(() -> { -// try { -// SAVE_RUNNER.shutdownNow(); -// SAVE_RUNNER.awaitTermination(100, TimeUnit.MILLISECONDS); -// } catch (Exception e) { -// // Ignore; we're shutting down -// } -// })); -// } - - /** - * Whether FileStore attributes should be determined asynchronously - * - * @param async - * 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.6.2 - */ - public static void setBackground(boolean async) { - background.set(async); - } - - /** - * 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 - * - * @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(); - if (dir == null) { - return FALLBACK_FILESTORE_ATTRIBUTES; - } - FileStoreAttributes cached = attrCacheByPath.get(dir); - if (cached != null) { - return cached; - } - FileStoreAttributes attrs = getFileStoreAttributes(dir); - if (attrs == null) { - // Don't cache, result might be late - return FALLBACK_FILESTORE_ATTRIBUTES; - } - 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> 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 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 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()); - } - FileStoreAttributes newAttrs = c; - SAVE_RUNNER.execute( - () -> saveToConfig(s, newAttrs)); - } - attributes = Optional.of(c); - } finally { - lock.unlock(); - locks.remove(s); - } - return attributes; - }, FUTURE_RUNNER); - 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 - boolean runInBackground = background.get(); - Optional d = runInBackground ? f.get( - 100, TimeUnit.MILLISECONDS) : f.get(); - if (d.isPresent()) { - return d.get(); - } else if (runInBackground) { - // return null until measurement is finished - return null; - } - // fall through and return fallback - } catch (IOException | ExecutionException | CancellationException e) { - LOG.error(e.getMessage(), e); - } catch (TimeoutException | SecurityException e) { - // use fallback - } catch (InterruptedException e) { - LOG.error(e.getMessage(), e); - Thread.currentThread().interrupt(); - } - 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 deltas = new ArrayList<>(); - Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ - Instant end = Instant.now().plusSeconds(3); - try { - probe.toFile().deleteOnExit(); - 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 { - Path parent = p.getParent(); - if (parent != null) { - FileUtils.mkdirs(parent.toFile(), true); - } - try (Writer w = new OutputStreamWriter(Files.newOutputStream(p), - UTF_8)) { - w.write(body); - } - } - - private static String read(Path p) throws IOException { - byte[] body = IO.readFully(p.toFile()); - return new String(body, 0, body.length, UTF_8); - } - - private static Optional measureFsTimestampResolution( - FileStore s, Path dir) { - if (LOG.isDebugEnabled()) { - 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 { - probe.toFile().deleteOnExit(); - Files.createFile(probe); - Duration fsResolution = getFsResolution(s, dir, probe); - Duration clockResolution = measureClockResolution(); - fsResolution = fsResolution.plus(clockResolution); - if (LOG.isDebugEnabled()) { - LOG.debug( - "{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$ - Thread.currentThread(), s, dir, fsResolution); - } - return Optional.of(fsResolution); - } catch (SecurityException e) { - // Log it here; most likely deleteProbe() below will also run - // into a SecurityException, and then this one will be lost - // without trace. - LOG.warn(e.getLocalizedMessage(), e); - } 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 getFsResolution(FileStore s, Path dir, - Path probe) throws IOException { - File probeFile = probe.toFile(); - FileTime t1 = Files.getLastModifiedTime(probe); - Instant t1i = t1.toInstant(); - FileTime t2; - Duration last = FALLBACK_TIMESTAMP_RESOLUTION; - long minScale = MINIMUM_RESOLUTION_NANOS; - long scale = ONE_SECOND; - long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis()); - long low = 0; - // Try up-front at microsecond and millisecond - long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND }; - for (long interval : tries) { - if (interval >= ONE_MILLISECOND) { - probeFile.setLastModified( - t1i.plusNanos(interval).toEpochMilli()); - } else { - Files.setLastModifiedTime(probe, - FileTime.from(t1i.plusNanos(interval))); - } - t2 = Files.getLastModifiedTime(probe); - if (t2.compareTo(t1) > 0) { - Duration diff = Duration.between(t1i, t2.toInstant()); - if (!diff.isZero() && !diff.isNegative() - && diff.compareTo(last) < 0) { - scale = interval; - high = 1; - last = diff; - break; - } - } else { - // Makes no sense going below - minScale = Math.max(minScale, interval); - } - } - // Binary search loop - while (high > low) { - long mid = (high + low) / 2; - if (mid == 0) { - // Smaller than current scale. Adjust scale. - long newScale = scale / 10; - if (newScale < minScale) { - break; - } - high *= scale / newScale; - low *= scale / newScale; - scale = newScale; - mid = (high + low) / 2; - } - long delta = mid * scale; - if (scale >= ONE_MILLISECOND) { - probeFile.setLastModified( - t1i.plusNanos(delta).toEpochMilli()); - } else { - Files.setLastModifiedTime(probe, - FileTime.from(t1i.plusNanos(delta))); - } - t2 = Files.getLastModifiedTime(probe); - int cmp = t2.compareTo(t1); - if (cmp > 0) { - high = mid; - Duration diff = Duration.between(t1i, t2.toInstant()); - if (diff.isZero() || diff.isNegative()) { - LOG.warn(JGitText.get().logInconsistentFiletimeDiff, - Thread.currentThread(), s, dir, t2, t1, diff, - last); - break; - } else if (diff.compareTo(last) > 0) { - LOG.warn(JGitText.get().logLargerFiletimeDiff, - Thread.currentThread(), s, dir, diff, last); - break; - } - last = diff; - } else if (cmp < 0) { - LOG.warn(JGitText.get().logSmallerFiletime, - Thread.currentThread(), s, dir, t2, t1, last); - break; - } else { - // No discernible difference - low = mid + 1; - } - } - return last; - } - - 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 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 jgitConfig; - try { - jgitConfig = SystemReader.getInstance().getJGitConfig(); - } 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 { - jgitConfig.setString( - ConfigConstants.CONFIG_FILESYSTEM_SECTION, key, - ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, - String.format("%d %s", //$NON-NLS-1$ - Long.valueOf(resolutionValue), - resolutionUnit.name().toLowerCase())); - jgitConfig.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())); - jgitConfig.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$ - jgitConfig, Integer.valueOf(retries), - Integer.valueOf(max_retries)); - } else { - LOG.warn(MessageFormat.format( - JGitText.get().lockFailedRetry, jgitConfig, - Integer.valueOf(retries))); - } - } catch (InterruptedException e1) { - Thread.currentThread().interrupt(); - break; - } - } catch (IOException e) { - LOG.error(MessageFormat.format( - JGitText.get().cannotSaveConfig, jgitConfig), e); - break; - } - } - } - - private static String getConfigKey(FileStore s) { - 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 JAVA_VERSION_PREFIX + 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; - - /** - * @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; - } - - /** - * @return the measured filesystem timestamp resolution - */ - @NonNull - public Duration getFsTimestampResolution() { - return 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. */ - public static final FS DETECTED = detect(); - - private static volatile FSFactory factory; - - /** - * 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 - * - * @param async - * 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 - * @deprecated Use {@link FileStoreAttributes#setBackground} instead - */ - @Deprecated - public static void setAsyncFileStoreAttributes(boolean async) { - FileStoreAttributes.setBackground(async); - } - - /** - * 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 - *
    - *
  • 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. - * @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. - * - * @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 userHome; - - private volatile Holder gitSystemConfig; - - /** - * Constructs a file system abstraction. - */ - protected FS() { - // Do nothing by default. - } - - /** - * 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. - * - * @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? - * - * @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. - * - * @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. - * - * @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 = Files.createTempFile("tempsymlinktarget", "").toFile(); //$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) { - LOG.error(JGitText.get().cannotDeleteFile, tempFile); - } - } - } - } - - /** - * 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. - * - * @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. - * - * @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. - * - * @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. - * - * @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. - * - * @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. - *

- * For symlinks it sets the modified time of the link 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. - *

- * For symlinks it sets the modified time of the link 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. - * - * @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. - * - * @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. - * - * @param dir - * directory relative to which the path name is. - * @param name - * path name to translate. - * @return the translated path. new File(dir,name) if this - * platform does not require path name translation. - */ - public File resolve(File dir, String name) { - 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. - * - * @return the user's home directory; null if the user does not have one. - */ - public File userHome() { - Holder p = userHome; - if (p == null) { - p = new Holder<>(safeUserHomeImpl()); - userHome = p; - } - return p.value; - } - - private File safeUserHomeImpl() { - File home; - try { - home = userHomeImpl(); - if (home != null) { - home.toPath(); - return home; - } - } catch (RuntimeException e) { - LOG.error(JGitText.get().exceptionWhileFindingUserHome, e); - } - home = defaultUserHomeImpl(); - if (home != null) { - try { - home.toPath(); - return home; - } catch (InvalidPathException e) { - LOG.error(MessageFormat - .format(JGitText.get().invalidHomeDirectory, home), e); - } - } - return null; - } - - /** - * 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? - * - * @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. - * - * @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). - * - * @return the user's home directory; null if the user does not have one. - */ - protected File userHomeImpl() { - return defaultUserHomeImpl(); - } - - private File defaultUserHomeImpl() { - String home = AccessController.doPrivileged( - (PrivilegedAction) () -> 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 which is executable. 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) { - File file = new File(p, command); - try { - if (file.isFile() && file.canExecute()) { - 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 - * - * @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 - * - * @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 env) - throws CommandFailedException { - 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; - } - 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 errorMessage = new AtomicReference<>(); - final AtomicReference exception = new AtomicReference<>(); - - GobblerThread(Process p, String[] command, File dir) { - this.p = p; - this.desc = Arrays.toString(command); - this.dir = String.valueOf(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. - * - * @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 - * - * @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[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$ - 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; - } - - if (parseVersion(v) < makeVersion(2, 8, 0)) { - // --show-origin was introduced in git 2.8.0. For older git: trick - // it into printing the path to the config file by using "echo" as - // the editor. - Map env = new HashMap<>(); - env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$ - - String w; - try { - // This command prints the path even if it doesn't exist - w = readPipe(gitExe.getParentFile(), - new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ - "--edit" }, //$NON-NLS-1$ - Charset.defaultCharset().name(), - env); - } catch (CommandFailedException e) { - LOG.warn(e.getMessage()); - return null; - } - if (StringUtils.isEmptyOrNull(w)) { - return null; - } - - return new File(w); - } - String w; - try { - w = readPipe(gitExe.getParentFile(), - new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ - "--show-origin", "--list", "-z" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - Charset.defaultCharset().name()); - } catch (CommandFailedException e) { - // This command fails if the system config doesn't exist - if (LOG.isDebugEnabled()) { - LOG.debug(e.getMessage()); - } - return null; - } - if (w == null) { - return null; - } - // We get NUL-terminated items; the first one will be a file name, - // prefixed by "file:". (Using -z is crucial, otherwise git quotes file - // names with special characters.) - int nul = w.indexOf(0); - if (nul <= 0) { - return null; - } - w = w.substring(0, nul); - int colon = w.indexOf(':'); - if (colon < 0) { - return null; - } - w = w.substring(colon + 1); - return w.isEmpty() ? null : new File(w); - } - - private long parseVersion(String version) { - Matcher m = VERSION.matcher(version); - if (m.find()) { - try { - return makeVersion( - Integer.parseInt(m.group(1)), - Integer.parseInt(m.group(2)), - Integer.parseInt(m.group(3))); - } catch (NumberFormatException e) { - // Ignore - } - } - return -1; - } - - private long makeVersion(int major, int minor, int patch) { - return ((major * 10_000L) + minor) * 10_000L + patch; - } - - /** - * 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. - * - * @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 - * - * @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 - * - * @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). - * - * @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 - * - * @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. - * - * @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. - * - * @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 - * - * @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. - * - * @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 - * - * @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 {@link java.io.File#createNewFile()}. Subclasses - * of this class may take care to provide a safe implementation for this - * even if {@link #supportsAtomicCreateNewFile()} is false - * - * @param path - * the file to be created - * @return true if the file was created, false 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 - * {@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 link; - - LockToken(boolean isCreated, Optional link) { - this.isCreated = isCreated; - this.link = link; - } - - /** - * @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$ - : "]"); //$NON-NLS-1$ - } - } - - /** - * 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 false - * - * @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()); - } - - /** - * See - * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. - * - * @param base - * The path against which other should be - * relativized. - * @param other - * The path that will be made relative to base. - * @return A relative path that, when resolved against base, - * will yield the original other. - * @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. - * - * @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) { - File[] all = directory.listFiles(); - if (all == null) { - return NO_ENTRIES; - } - 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. - *

- * - * @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 null, - * 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, - 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. - * - * @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 null, - * but can be an empty array. - * @param 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. - * @param 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. - * @param stdinArgs - * A string to pass on to the standard input of the hook. May be - * null. - * @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 5.11 - */ - public ProcessResult runHookIfPresent(Repository repository, - String hookName, String[] args, OutputStream outRedirect, - OutputStream errRedirect, String stdinArgs) - throws JGitInternalException { - return new ProcessResult(Status.NOT_SUPPORTED); - } - - /** - * See - * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, 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 null, - * but can be an empty array. - * @param 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. - * @param 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. - * @param stdinArgs - * A string to pass on to the standard input of the hook. May be - * null. - * @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 5.11 - */ - protected ProcessResult internalRunHookIfPresent(Repository repository, - String hookName, String[] args, OutputStream outRedirect, - OutputStream errRedirect, String stdinArgs) - throws JGitInternalException { - File hookFile = findHook(repository, hookName); - if (hookFile == null || hookName == null) { - return new ProcessResult(Status.NOT_PRESENT); - } - - File runDirectory = getRunDirectory(repository, hookName); - if (runDirectory == null) { - return new ProcessResult(Status.NOT_PRESENT); - } - String cmd = hookFile.getAbsolutePath(); - ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args); - hookProcess.directory(runDirectory.getAbsoluteFile()); - Map 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); - } - } - - /** - * Quote a string (such as a file system path obtained from a Java - * {@link File} or {@link Path} object) such that it can be passed as first - * argument to {@link #runInShell(String, String[])}. - *

- * This default implementation returns the string unchanged. - *

- * - * @param cmd - * the String to quote - * @return the quoted string - */ - String shellQuote(String cmd) { - return cmd; - } - - /** - * 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, null otherwise. - * @since 4.0 - */ - public File findHook(Repository repository, String hookName) { - if (hookName == null) { - return null; - } - File hookDir = getHooksDirectory(repository); - if (hookDir == null) { - return null; - } - File hookFile = new File(hookDir, hookName); - if (hookFile.isAbsolute()) { - if (!hookFile.exists() || (FS.DETECTED.supportsExecute() - && !FS.DETECTED.canExecute(hookFile))) { - return null; - } - } else { - try { - File runDirectory = getRunDirectory(repository, hookName); - if (runDirectory == null) { - return null; - } - Path hookPath = runDirectory.getAbsoluteFile().toPath() - .resolve(hookFile.toPath()); - FS fs = repository.getFS(); - if (fs == null) { - fs = FS.DETECTED; - } - if (!Files.exists(hookPath) || (fs.supportsExecute() - && !fs.canExecute(hookPath.toFile()))) { - return null; - } - hookFile = hookPath.toFile(); - } catch (InvalidPathException e) { - LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath, - hookFile)); - return null; - } - } - return hookFile; - } - - private File getRunDirectory(Repository repository, - @NonNull String hookName) { - if (repository.isBare()) { - return repository.getDirectory(); - } - switch (hookName) { - case "pre-receive": //$NON-NLS-1$ - case "update": //$NON-NLS-1$ - case "post-receive": //$NON-NLS-1$ - case "post-update": //$NON-NLS-1$ - case "push-to-checkout": //$NON-NLS-1$ - return repository.getDirectory(); - default: - return repository.getWorkTree(); - } - } - - private File getHooksDirectory(Repository repository) { - Config config = repository.getConfig(); - String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION, - null, ConfigConstants.CONFIG_KEY_HOOKS_PATH); - if (hooksDir != null) { - return new File(hooksDir); - } - File dir = repository.getDirectory(); - return dir == null ? null : new File(dir, Constants.HOOKS); - } - - /** - * 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 null, in which case the processes standard - * output will be lost. - * @param errRedirect - * A OutputStream on which to redirect the processes stderr. Can - * be null, 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 - * null. - * @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. - * - * @param processBuilder - * The process builder configured for this process. - * @param outRedirect - * An OutputStream on which to redirect the processes stdout. Can - * be null, in which case the processes standard - * output will be lost. - * @param errRedirect - * An OutputStream on which to redirect the processes stderr. Can - * be null, in which case the processes standard - * error will be lost. - * @param 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. - * @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 { - 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 {@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. - *

- * - * @param pool - * the pool to shutdown - * @return true if the pool has been properly shutdown, - * false 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. - * - * @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 {@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 { - final V value; - - Holder(V value) { - this.value = value; - } - } - - /** - * File attributes we typically care for. - * - * @since 3.3 - */ - public static class Attributes { - - /** - * @return true if this are the attributes of a directory - */ - public boolean isDirectory() { - return isDirectory; - } - - /** - * @return true if this are the attributes of an executable file - */ - public boolean isExecutable() { - return isExecutable; - } - - /** - * @return true if this are the attributes of a symbolic link - */ - public boolean isSymbolicLink() { - return isSymbolicLink; - } - - /** - * @return true if this are the attributes of a regular file - */ - public boolean isRegularFile() { - return isRegularFile; - } - - /** - * @return the time when the file was created - */ - public long getCreationTime() { - return creationTime; - } - - /** - * @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(); - } - - /** - * @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 - */ - 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. - * - * @param fs - * @param path - */ - public Attributes(File path, FS fs) { - this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L); - } - - /** - * @return length of this file object - */ - public long getLength() { - if (length == -1) - return length = file.length(); - return length; - } - - /** - * @return the filename - */ - public String getName() { - return file.getName(); - } - - /** - * @return the file the attributes apply to - */ - public File getFile() { - return file; - } - - boolean exists() { - return exists; - } - } - - /** - * 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. - * - * @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. - * - * @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 {@link StreamGobbler} will never close either of its - * streams. - *

- */ - 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; - } - } - } - } - } -} diff --git a/rewrite-core/src/main/java/org/openrewrite/Recipe.java b/rewrite-core/src/main/java/org/openrewrite/Recipe.java index fc7603d8c98..596c5f1c8eb 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Recipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/Recipe.java @@ -31,8 +31,6 @@ import org.openrewrite.table.RecipeRunStats; import org.openrewrite.table.SourcesFileErrors; import org.openrewrite.table.SourcesFileResults; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.net.URI; @@ -60,8 +58,6 @@ public abstract class Recipe implements Cloneable { public static final String PANIC = "__AHHH_PANIC!!!__"; - private static final Logger logger = LoggerFactory.getLogger(Recipe.class); - @SuppressWarnings("unused") @JsonProperty("@c") public String getJacksonPolymorphicTypeTag() { @@ -369,7 +365,7 @@ public Validated validate() { try { validated = validated.and(Validated.required(clazz.getSimpleName() + '.' + field.getName(), field.get(this))); } catch (IllegalAccessException e) { - logger.warn("Unable to validate the field [{}] on the class [{}]", field.getName(), clazz.getName()); + validated = Validated.invalid(field.getName(), null, "Unable to access " + clazz.getName() + "." + field.getName(), e); } } for (Recipe recipe : getRecipeList()) { diff --git a/rewrite-core/src/main/java/org/openrewrite/Result.java b/rewrite-core/src/main/java/org/openrewrite/Result.java index 5858223f0d2..490d648898f 100755 --- a/rewrite-core/src/main/java/org/openrewrite/Result.java +++ b/rewrite-core/src/main/java/org/openrewrite/Result.java @@ -16,7 +16,7 @@ package org.openrewrite; import lombok.Getter; -import org.eclipse.jgit.lib.FileMode; +import org.openrewrite.jgit.lib.FileMode; import org.openrewrite.config.RecipeDescriptor; import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.lang.Nullable; diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 5d314289b98..c92080e538f 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -27,8 +27,6 @@ import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.style.NamedStyles; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; @@ -39,7 +37,6 @@ import static org.openrewrite.internal.RecipeIntrospectionUtils.constructRecipe; public class ClasspathScanningLoader implements ResourceLoader { - private static final Logger logger = LoggerFactory.getLogger(ClasspathScanningLoader.class); private final LinkedHashSet recipes = new LinkedHashSet<>(); private final List styles = new ArrayList<>(); @@ -147,15 +144,16 @@ private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) { for (ClassInfo classInfo : result.getSubclasses(NamedStyles.class.getName())) { Class styleClass = classInfo.loadClass(); - try { Constructor constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass); if (constructor != null) { constructor.setAccessible(true); - styles.add((NamedStyles) constructor.newInstance()); + try { + styles.add((NamedStyles) constructor.newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } } - } catch (Throwable e) { - logger.warn("Unable to configure {}", styleClass.getName(), e); - } + } } } @@ -178,7 +176,6 @@ private void configureRecipes(ScanResult result, String className) { MetricsHelper.successTags(builder.tags("recipe", "elided")); } catch (Throwable e) { MetricsHelper.errorTags(builder.tags("recipe", recipeClass.getName()), e); - logger.warn("Unable to configure {}", recipeClass.getName(), e); } finally { sample.stop(builder.register(Metrics.globalRegistry)); } diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 9050c6f5ac8..5ca57741d2b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -30,8 +30,6 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.style.NamedStyles; import org.openrewrite.style.Style; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -54,7 +52,6 @@ import static org.openrewrite.Validated.invalid; public class YamlResourceLoader implements ResourceLoader { - private static final Logger logger = LoggerFactory.getLogger(YamlResourceLoader.class); int refCount = 0; private static final PropertyPlaceholderHelper propertyPlaceholderHelper = @@ -170,17 +167,13 @@ public YamlResourceLoader(InputStream yamlInput, URI source, Properties properti private Collection> loadResources(ResourceType resourceType) { Collection> resources = new ArrayList<>(); Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); - try { - for (Object resource : yaml.loadAll(yamlSource)) { - if (resource instanceof Map) { - @SuppressWarnings("unchecked") Map resourceMap = (Map) resource; - if (resourceType.equals(ResourceType.fromSpec((String) resourceMap.get("type")))) { - resources.add(resourceMap); - } + for (Object resource : yaml.loadAll(yamlSource)) { + if (resource instanceof Map) { + @SuppressWarnings("unchecked") Map resourceMap = (Map) resource; + if (resourceType.equals(ResourceType.fromSpec((String) resourceMap.get("type")))) { + resources.add(resourceMap); } } - } catch (Exception e) { - logger.error("Loading yaml {} type failed, yaml source: {}", resourceType, yamlSource, e); } return resources; } diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java index 42fa06de793..3c8a1ae01c2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java @@ -15,12 +15,12 @@ */ package org.openrewrite.internal; -import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.diff.RawTextComparator; -import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; -import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; -import org.eclipse.jgit.lib.*; +import org.openrewrite.jgit.diff.DiffEntry; +import org.openrewrite.jgit.diff.DiffFormatter; +import org.openrewrite.jgit.diff.RawTextComparator; +import org.openrewrite.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.openrewrite.jgit.internal.storage.dfs.InMemoryRepository; +import org.openrewrite.jgit.lib.*; import org.openrewrite.Recipe; import org.openrewrite.internal.lang.Nullable; diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java index 2063bcc60dc..3aa571be370 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java @@ -18,13 +18,13 @@ import lombok.Value; import lombok.With; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.*; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.transport.RemoteConfig; -import org.eclipse.jgit.transport.URIish; -import org.eclipse.jgit.treewalk.WorkingTreeOptions; +import org.openrewrite.jgit.api.Git; +import org.openrewrite.jgit.api.errors.GitAPIException; +import org.openrewrite.jgit.lib.*; +import org.openrewrite.jgit.revwalk.RevCommit; +import org.openrewrite.jgit.transport.RemoteConfig; +import org.openrewrite.jgit.transport.URIish; +import org.openrewrite.jgit.treewalk.WorkingTreeOptions; import org.openrewrite.Incubating; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.ci.BuildEnvironment; diff --git a/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java b/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java index 23cf5929c3d..d006926eb0f 100644 --- a/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java @@ -15,7 +15,7 @@ */ package org.openrewrite.internal; -import org.eclipse.jgit.lib.FileMode; +import org.openrewrite.jgit.lib.FileMode; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java index 0a1e300bb88..915ddb2f7f1 100644 --- a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java @@ -15,13 +15,13 @@ */ package org.openrewrite.marker; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.RepositoryCache; -import org.eclipse.jgit.transport.TagOpt; -import org.eclipse.jgit.transport.URIish; -import org.eclipse.jgit.util.FS; +import org.openrewrite.jgit.api.Git; +import org.openrewrite.jgit.api.errors.GitAPIException; +import org.openrewrite.jgit.lib.Constants; +import org.openrewrite.jgit.lib.RepositoryCache; +import org.openrewrite.jgit.transport.TagOpt; +import org.openrewrite.jgit.transport.URIish; +import org.openrewrite.jgit.util.FS; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -44,7 +44,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_BRANCH_SECTION; +import static org.openrewrite.jgit.lib.ConfigConstants.CONFIG_BRANCH_SECTION; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.openrewrite.Tree.randomId; diff --git a/rewrite-core/src/test/java/org/openrewrite/quark/QuarkParserTest.java b/rewrite-core/src/test/java/org/openrewrite/quark/QuarkParserTest.java index 9f67ca42737..d1806902c48 100644 --- a/rewrite-core/src/test/java/org/openrewrite/quark/QuarkParserTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/quark/QuarkParserTest.java @@ -15,8 +15,8 @@ */ package org.openrewrite.quark; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; +import org.openrewrite.jgit.api.Git; +import org.openrewrite.jgit.api.errors.GitAPIException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.openrewrite.*; diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index a6b24acd615..126a398663e 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -21,7 +21,6 @@ dependencies { testRuntimeOnly("org.apache.commons:commons-lang3:latest.release") testRuntimeOnly(project(":rewrite-yaml")) testImplementation(project(":rewrite-maven")) - testImplementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") } tasks.withType { diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index dc57aa0ed8a..97c6b600041 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { implementation("org.assertj:assertj-core:latest.release") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") - implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") testImplementation(project(":rewrite-groovy")) }