Merge branch 'stable-5.1' into stable-5.2

* stable-5.1:
  Fix OpenSshConfigTest#config
  FileSnapshot: fix bug with timestamp thresholding
  In LockFile#waitForStatChange wait in units of file time resolution
  Cache FileStoreAttributeCache per directory
  Fix FileSnapshot#save(long) and FileSnapshot#save(Instant)
  Persist minimal racy threshold and allow manual configuration
  Measure minimum racy interval to auto-configure FileSnapshot
  Reuse FileUtils to recursively delete files created by tests
  Fix FileAttributeCache.toString()
  Add test for racy git detection in FileSnapshot
  Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times
  Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit
  Add missing javadoc in org.eclipse.jgit.junit
  Enhance RepeatRule to report number of failures at the end
  Fix FileSnapshotTests for filesystem with high timestamp resolution
  Retry deleting test files in FileBasedConfigTest
  Measure filesystem timestamp resolution already in test setup
  Refactor FileSnapshotTest to use NIO APIs
  Measure stored timestamp resolution instead of time to touch file
  Handle CancellationException in FileStoreAttributeCache
  Fix FileSnapshot#saveNoConfig
  Use Instant for smudge time in DirCache and DirCacheEntry
  Use Instant instead of milliseconds for filesystem timestamp handling
  Workaround SecurityException in FS#getFsTimestampResolution
  Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution
  FS: ignore AccessDeniedException when measuring timestamp resolution
  Add debug trace for FileSnapshot
  Use FileChannel.open to touch file and set mtime to now
  Persist filesystem timestamp resolution and allow manual configuration
  Increase bazel timeout for long running tests
  Bazel: Fix lint warning flagged by buildifier
  Update bazlets to latest version
  Bazel: Add missing dependencies for ArchiveCommandTest
  Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file
  Add support for nanoseconds and microseconds for Config#getTimeUnit
  Optionally measure filesystem timestamp resolution asynchronously
  Delete unused FileTreeIteratorWithTimeControl
  FileSnapshot#equals: consider UNKNOWN_SIZE
  Timeout measuring file timestamp resolution after 2 seconds
  Fix RacyGitTests#testRacyGitDetection
  Change RacyGitTests to create a racy git situation in a stable way
  Deprecate Constants.CHARACTER_ENCODING in favor of StandardCharsets.UTF_8
  Fix non-deterministic hash of archives created by ArchiveCommand
  Update Maven plugins ecj, plexus, error-prone
  Update Maven plugins and cleanup Maven warnings
  Make inner classes static where possible
  Fix API problem filters

Change-Id: Ia57385b2a60f48a5317c8d723721c235d7043a84
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Matthias Sohn 2019-08-08 11:18:14 +02:00
commit 0046b2a8fe
78 changed files with 3046 additions and 748 deletions

View File

@ -15,7 +15,7 @@ versions.check(minimum_bazel_version = "0.19.0")
load("//tools:bazlets.bzl", "load_bazlets") load("//tools:bazlets.bzl", "load_bazlets")
load_bazlets(commit = "3afbeab55ece585dbfc7a980bf7214b24ddbbe86") load_bazlets(commit = "8528a0df69dadf6311d8d3f81c1b693afda8bcf1")
load( load(
"@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", "@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl",

View File

@ -12,6 +12,7 @@ java_library(
visibility = [ visibility = [
"//org.eclipse.jgit.archive:__pkg__", "//org.eclipse.jgit.archive:__pkg__",
"//org.eclipse.jgit.pgm.test:__pkg__", "//org.eclipse.jgit.pgm.test:__pkg__",
"//org.eclipse.jgit.test:__pkg__",
], ],
exports = ["@commons-compress//jar"], exports = ["@commons-compress//jar"],
) )

View File

@ -65,12 +65,13 @@ public class GitCloneTaskTest extends LocalDiskRepositoryTestCase {
@Before @Before
public void before() throws IOException { public void before() throws IOException {
dest = createTempFile();
FS.getFileStoreAttributes(dest.toPath().getParent());
project = new Project(); project = new Project();
project.init(); project.init();
enableLogging(); enableLogging();
project.addTaskDefinition("git-clone", GitCloneTask.class); project.addTaskDefinition("git-clone", GitCloneTask.class);
task = (GitCloneTask) project.createTask("git-clone"); task = (GitCloneTask) project.createTask("git-clone");
dest = createTempFile();
task.setDest(dest); task.setDest(dest);
} }

View File

@ -58,12 +58,14 @@
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.Enumeration; import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.FS;
/** /**
* Dumps a file over HTTP GET (or its information via HEAD). * Dumps a file over HTTP GET (or its information via HEAD).
@ -76,7 +78,7 @@ final class FileSender {
private final RandomAccessFile source; private final RandomAccessFile source;
private final long lastModified; private final Instant lastModified;
private final long fileLen; private final long fileLen;
@ -89,7 +91,7 @@ final class FileSender {
this.source = new RandomAccessFile(path, "r"); this.source = new RandomAccessFile(path, "r");
try { try {
this.lastModified = path.lastModified(); this.lastModified = FS.DETECTED.lastModifiedInstant(path);
this.fileLen = source.getChannel().size(); this.fileLen = source.getChannel().size();
this.end = fileLen; this.end = fileLen;
} catch (IOException e) { } catch (IOException e) {
@ -114,7 +116,7 @@ void close() {
} }
} }
long getLastModified() { Instant getLastModified() {
return lastModified; return lastModified;
} }

View File

@ -54,6 +54,7 @@
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
@ -76,7 +77,9 @@ static class Loose extends ObjectFileServlet {
@Override @Override
String etag(FileSender sender) throws IOException { String etag(FileSender sender) throws IOException {
return Long.toHexString(sender.getLastModified()); Instant lastModified = sender.getLastModified();
return Long.toHexString(lastModified.getEpochSecond())
+ Long.toHexString(lastModified.getNano());
} }
} }
@ -145,7 +148,9 @@ private void serve(final HttpServletRequest req,
try { try {
final String etag = etag(sender); final String etag = etag(sender);
final long lastModified = (sender.getLastModified() / 1000) * 1000; // HTTP header Last-Modified header has a resolution of 1 sec, see
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29
final long lastModified = sender.getLastModified().getEpochSecond();
String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH); String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH);
if (etag != null && etag.equals(ifNoneMatch)) { if (etag != null && etag.equals(ifNoneMatch)) {

View File

@ -1,10 +1,13 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
@ -14,6 +17,7 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=warning org.eclipse.jdt.core.compiler.problem.autoboxing=warning
@ -48,7 +52,7 @@ org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
@ -64,14 +68,16 @@ org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=error org.eclipse.jdt.core.compiler.problem.nullReference=error
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
@ -86,16 +92,22 @@ org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled

View File

@ -51,6 +51,8 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -126,6 +128,10 @@ public void setUp() throws Exception {
if (!tmp.delete() || !tmp.mkdir()) if (!tmp.delete() || !tmp.mkdir())
throw new IOException("Cannot create " + tmp); throw new IOException("Cannot create " + tmp);
// measure timer resolution before the test to avoid time critical tests
// are affected by time needed for measurement
FS.getFileStoreAttributes(tmp.toPath().getParent());
mockSystemReader = new MockSystemReader(); mockSystemReader = new MockSystemReader();
mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp,
"usergitconfig"), FS.DETECTED); "usergitconfig"), FS.DETECTED);
@ -232,35 +238,30 @@ protected void recursiveDelete(File dir) {
private static boolean recursiveDelete(final File dir, private static boolean recursiveDelete(final File dir,
boolean silent, boolean failOnError) { boolean silent, boolean failOnError) {
assert !(silent && failOnError); assert !(silent && failOnError);
if (!dir.exists()) int options = FileUtils.RECURSIVE | FileUtils.RETRY
return silent; | FileUtils.SKIP_MISSING;
final File[] ls = dir.listFiles(); if (silent) {
if (ls != null) options |= FileUtils.IGNORE_ERRORS;
for (int k = 0; k < ls.length; k++) {
final File e = ls[k];
if (e.isDirectory())
silent = recursiveDelete(e, silent, failOnError);
else if (!e.delete()) {
if (!silent)
reportDeleteFailure(failOnError, e);
silent = !failOnError;
}
}
if (!dir.delete()) {
if (!silent)
reportDeleteFailure(failOnError, dir);
silent = !failOnError;
} }
return silent; try {
FileUtils.delete(dir, options);
} catch (IOException e) {
reportDeleteFailure(failOnError, dir, e);
return !failOnError;
}
return true;
} }
private static void reportDeleteFailure(boolean failOnError, File e) { private static void reportDeleteFailure(boolean failOnError, File f,
Exception cause) {
String severity = failOnError ? "ERROR" : "WARNING"; String severity = failOnError ? "ERROR" : "WARNING";
String msg = severity + ": Failed to delete " + e; String msg = severity + ": Failed to delete " + f;
if (failOnError) if (failOnError) {
fail(msg); fail(msg);
else } else {
System.err.println(msg); System.err.println(msg);
}
cause.printStackTrace(new PrintStream(System.err));
} }
/** Constant <code>MOD_TIME=1</code> */ /** Constant <code>MOD_TIME=1</code> */
@ -322,12 +323,13 @@ public static String indexState(Repository repo, int includedOptions)
throws IllegalStateException, IOException { throws IllegalStateException, IOException {
DirCache dc = repo.readDirCache(); DirCache dc = repo.readDirCache();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
TreeSet<Long> timeStamps = new TreeSet<>(); TreeSet<Instant> timeStamps = new TreeSet<>();
// iterate once over the dircache just to collect all time stamps // iterate once over the dircache just to collect all time stamps
if (0 != (includedOptions & MOD_TIME)) { if (0 != (includedOptions & MOD_TIME)) {
for (int i=0; i<dc.getEntryCount(); ++i) for (int i = 0; i < dc.getEntryCount(); ++i) {
timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
}
} }
// iterate again, now produce the result string // iterate again, now produce the result string
@ -339,7 +341,8 @@ public static String indexState(Repository repo, int includedOptions)
sb.append(", stage:" + stage); sb.append(", stage:" + stage);
if (0 != (includedOptions & MOD_TIME)) { if (0 != (includedOptions & MOD_TIME)) {
sb.append(", time:t"+ sb.append(", time:t"+
timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); timeStamps.headSet(entry.getLastModifiedInstant())
.size());
} }
if (0 != (includedOptions & SMUDGE)) if (0 != (includedOptions & SMUDGE))
if (entry.isSmudged()) if (entry.isSmudged())

View File

@ -56,4 +56,13 @@
* Number of repetitions * Number of repetitions
*/ */
public abstract int n(); public abstract int n();
/**
* Whether to abort execution on first test failure
*
* @return {@code true} if execution should be aborted on the first failure,
* otherwise count failures and continue execution
* @since 5.1.9
*/
public boolean abortOnFailure() default true;
} }

View File

@ -81,9 +81,31 @@ public class RepeatRule implements TestRule {
private static Logger LOG = Logger private static Logger LOG = Logger
.getLogger(RepeatRule.class.getName()); .getLogger(RepeatRule.class.getName());
/**
* Exception thrown if repeated execution of a test annotated with
* {@code @Repeat} failed.
*/
public static class RepeatedTestException extends RuntimeException { public static class RepeatedTestException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* Constructor
*
* @param message
* the error message
*/
public RepeatedTestException(String message) {
super(message);
}
/**
* Constructor
*
* @param message
* the error message
* @param cause
* exception causing this exception
*/
public RepeatedTestException(String message, Throwable cause) { public RepeatedTestException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
@ -93,28 +115,45 @@ private static class RepeatStatement extends Statement {
private final int repetitions; private final int repetitions;
private boolean abortOnFailure;
private final Statement statement; private final Statement statement;
private RepeatStatement(int repetitions, Statement statement) { private RepeatStatement(int repetitions, boolean abortOnFailure,
Statement statement) {
this.repetitions = repetitions; this.repetitions = repetitions;
this.abortOnFailure = abortOnFailure;
this.statement = statement; this.statement = statement;
} }
@Override @Override
public void evaluate() throws Throwable { public void evaluate() throws Throwable {
int failures = 0;
for (int i = 0; i < repetitions; i++) { for (int i = 0; i < repetitions; i++) {
try { try {
statement.evaluate(); statement.evaluate();
} catch (Throwable e) { } catch (Throwable e) {
failures += 1;
RepeatedTestException ex = new RepeatedTestException( RepeatedTestException ex = new RepeatedTestException(
MessageFormat.format( MessageFormat.format(
"Repeated test failed when run for the {0}. time", "Repeated test failed when run for the {0}. time",
Integer.valueOf(i + 1)), Integer.valueOf(i + 1)),
e); e);
LOG.log(Level.SEVERE, ex.getMessage(), ex); LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw ex; if (abortOnFailure) {
throw ex;
}
} }
} }
if (failures > 0) {
RepeatedTestException e = new RepeatedTestException(
MessageFormat.format(
"Test failed {0} times out of {1} repeated executions",
Integer.valueOf(failures),
Integer.valueOf(repetitions)));
LOG.log(Level.SEVERE, e.getMessage(), e);
throw e;
}
} }
} }
@ -125,7 +164,8 @@ public Statement apply(Statement statement, Description description) {
Repeat repeat = description.getAnnotation(Repeat.class); Repeat repeat = description.getAnnotation(Repeat.class);
if (repeat != null) { if (repeat != null) {
int n = repeat.n(); int n = repeat.n();
result = new RepeatStatement(n, statement); boolean abortOnFailure = repeat.abortOnFailure();
result = new RepeatStatement(n, abortOnFailure, statement);
} }
return result; return result;
} }

View File

@ -57,7 +57,9 @@
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
@ -284,7 +286,7 @@ protected void resetIndex(FileTreeIterator treeItr)
dce = new DirCacheEntry(treeItr.getEntryPathString()); dce = new DirCacheEntry(treeItr.getEntryPathString());
dce.setFileMode(treeItr.getEntryFileMode()); dce.setFileMode(treeItr.getEntryFileMode());
dce.setLastModified(treeItr.getEntryLastModified()); dce.setLastModified(treeItr.getEntryLastModifiedInstant());
dce.setLength((int) len); dce.setLength((int) len);
try (FileInputStream in = new FileInputStream( try (FileInputStream in = new FileInputStream(
treeItr.getEntryFile())) { treeItr.getEntryFile())) {
@ -361,7 +363,8 @@ public static String slashify(String str) {
* @throws InterruptedException * @throws InterruptedException
* @throws IOException * @throws IOException
*/ */
public static long fsTick(File lastFile) throws InterruptedException, public static Instant fsTick(File lastFile)
throws InterruptedException,
IOException { IOException {
File tmp; File tmp;
FS fs = FS.DETECTED; FS fs = FS.DETECTED;
@ -375,15 +378,16 @@ public static long fsTick(File lastFile) throws InterruptedException,
tmp = File.createTempFile("fsTickTmpFile", null, tmp = File.createTempFile("fsTickTmpFile", null,
lastFile.getParentFile()); lastFile.getParentFile());
} }
long res = FS.getFsTimerResolution(tmp.toPath()).toMillis(); long res = FS.getFileStoreAttributes(tmp.toPath())
.getFsTimestampResolution().toNanos();
long sleepTime = res / 10; long sleepTime = res / 10;
try { try {
long startTime = fs.lastModified(lastFile); Instant startTime = fs.lastModifiedInstant(lastFile);
long actTime = fs.lastModified(tmp); Instant actTime = fs.lastModifiedInstant(tmp);
while (actTime <= startTime) { while (actTime.compareTo(startTime) <= 0) {
Thread.sleep(sleepTime); TimeUnit.NANOSECONDS.sleep(sleepTime);
FileUtils.touch(tmp.toPath()); FileUtils.touch(tmp.toPath());
actTime = fs.lastModified(tmp); actTime = fs.lastModifiedInstant(tmp);
} }
return actTime; return actTime;
} finally { } finally {

View File

@ -1059,6 +1059,14 @@ public class CommitBuilder {
parents.add(prior.create()); parents.add(prior.create());
} }
/**
* set parent commit
*
* @param p
* parent commit
* @return this commit builder
* @throws Exception
*/
public CommitBuilder parent(RevCommit p) throws Exception { public CommitBuilder parent(RevCommit p) throws Exception {
if (parents.isEmpty()) { if (parents.isEmpty()) {
DirCacheBuilder b = tree.builder(); DirCacheBuilder b = tree.builder();
@ -1071,29 +1079,71 @@ public CommitBuilder parent(RevCommit p) throws Exception {
return this; return this;
} }
/**
* Get parent commits
*
* @return parent commits
*/
public List<RevCommit> parents() { public List<RevCommit> parents() {
return Collections.unmodifiableList(parents); return Collections.unmodifiableList(parents);
} }
/**
* Remove parent commits
*
* @return this commit builder
*/
public CommitBuilder noParents() { public CommitBuilder noParents() {
parents.clear(); parents.clear();
return this; return this;
} }
/**
* Remove files
*
* @return this commit builder
*/
public CommitBuilder noFiles() { public CommitBuilder noFiles() {
tree.clear(); tree.clear();
return this; return this;
} }
/**
* Set top level tree
*
* @param treeId
* the top level tree
* @return this commit builder
*/
public CommitBuilder setTopLevelTree(ObjectId treeId) { public CommitBuilder setTopLevelTree(ObjectId treeId) {
topLevelTree = treeId; topLevelTree = treeId;
return this; return this;
} }
/**
* Add file with given content
*
* @param path
* path of the file
* @param content
* the file content
* @return this commit builder
* @throws Exception
*/
public CommitBuilder add(String path, String content) throws Exception { public CommitBuilder add(String path, String content) throws Exception {
return add(path, blob(content)); return add(path, blob(content));
} }
/**
* Add file with given path and blob
*
* @param path
* path of the file
* @param id
* blob for this file
* @return this commit builder
* @throws Exception
*/
public CommitBuilder add(String path, RevBlob id) public CommitBuilder add(String path, RevBlob id)
throws Exception { throws Exception {
return edit(new PathEdit(path) { return edit(new PathEdit(path) {
@ -1105,6 +1155,13 @@ public void apply(DirCacheEntry ent) {
}); });
} }
/**
* Edit the index
*
* @param edit
* the index record update
* @return this commit builder
*/
public CommitBuilder edit(PathEdit edit) { public CommitBuilder edit(PathEdit edit) {
DirCacheEditor e = tree.editor(); DirCacheEditor e = tree.editor();
e.add(edit); e.add(edit);
@ -1112,6 +1169,13 @@ public CommitBuilder edit(PathEdit edit) {
return this; return this;
} }
/**
* Remove a file
*
* @param path
* path of the file
* @return this commit builder
*/
public CommitBuilder rm(String path) { public CommitBuilder rm(String path) {
DirCacheEditor e = tree.editor(); DirCacheEditor e = tree.editor();
e.add(new DeletePath(path)); e.add(new DeletePath(path));
@ -1120,49 +1184,111 @@ public CommitBuilder rm(String path) {
return this; return this;
} }
/**
* Set commit message
*
* @param m
* the message
* @return this commit builder
*/
public CommitBuilder message(String m) { public CommitBuilder message(String m) {
message = m; message = m;
return this; return this;
} }
/**
* Get the commit message
*
* @return the commit message
*/
public String message() { public String message() {
return message; return message;
} }
/**
* Tick the clock
*
* @param secs
* number of seconds
* @return this commit builder
*/
public CommitBuilder tick(int secs) { public CommitBuilder tick(int secs) {
tick = secs; tick = secs;
return this; return this;
} }
/**
* Set author and committer identity
*
* @param ident
* identity to set
* @return this commit builder
*/
public CommitBuilder ident(PersonIdent ident) { public CommitBuilder ident(PersonIdent ident) {
author = ident; author = ident;
committer = ident; committer = ident;
return this; return this;
} }
/**
* Set the author identity
*
* @param a
* the author's identity
* @return this commit builder
*/
public CommitBuilder author(PersonIdent a) { public CommitBuilder author(PersonIdent a) {
author = a; author = a;
return this; return this;
} }
/**
* Get the author identity
*
* @return the author identity
*/
public PersonIdent author() { public PersonIdent author() {
return author; return author;
} }
/**
* Set the committer identity
*
* @param c
* the committer identity
* @return this commit builder
*/
public CommitBuilder committer(PersonIdent c) { public CommitBuilder committer(PersonIdent c) {
committer = c; committer = c;
return this; return this;
} }
/**
* Get the committer identity
*
* @return the committer identity
*/
public PersonIdent committer() { public PersonIdent committer() {
return committer; return committer;
} }
/**
* Insert changeId
*
* @return this commit builder
*/
public CommitBuilder insertChangeId() { public CommitBuilder insertChangeId() {
changeId = ""; changeId = "";
return this; return this;
} }
/**
* Insert given changeId
*
* @param c
* changeId
* @return this commit builder
*/
public CommitBuilder insertChangeId(String c) { public CommitBuilder insertChangeId(String c) {
// Validate, but store as a string so we can use "" as a sentinel. // Validate, but store as a string so we can use "" as a sentinel.
ObjectId.fromString(c); ObjectId.fromString(c);
@ -1170,6 +1296,13 @@ public CommitBuilder insertChangeId(String c) {
return this; return this;
} }
/**
* Create the commit
*
* @return the new commit
* @throws Exception
* if creation failed
*/
public RevCommit create() throws Exception { public RevCommit create() throws Exception {
if (self == null) { if (self == null) {
TestRepository.this.tick(tick); TestRepository.this.tick(tick);
@ -1230,6 +1363,12 @@ private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
+ cid.getName() + "\n"); //$NON-NLS-1$ + cid.getName() + "\n"); //$NON-NLS-1$
} }
/**
* Create child commit builder
*
* @return child commit builder
* @throws Exception
*/
public CommitBuilder child() throws Exception { public CommitBuilder child() throws Exception {
return new CommitBuilder(this); return new CommitBuilder(this);
} }

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* 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.junit.time;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import org.eclipse.jgit.util.FS;
/**
* Utility methods for handling timestamps
*/
public class TimeUtil {
/**
* Set the lastModified time of a given file by adding a given offset to the
* current lastModified time
*
* @param path
* path of a file to set last modified
* @param offsetMillis
* offset in milliseconds, if negative the new lastModified time
* is offset before the original lastModified time, otherwise
* after the original time
* @return the new lastModified time
*/
public static Instant setLastModifiedWithOffset(Path path,
long offsetMillis) {
Instant mTime = FS.DETECTED.lastModifiedInstant(path)
.plusMillis(offsetMillis);
try {
Files.setLastModifiedTime(path, FileTime.from(mTime));
return mTime;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Set the lastModified time of file a to the one from file b
*
* @param a
* file to set lastModified time
* @param b
* file to read lastModified time from
*/
public static void setLastModifiedOf(Path a, Path b) {
Instant mTime = FS.DETECTED.lastModifiedInstant(b);
try {
Files.setLastModifiedTime(a, FileTime.from(mTime));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

@ -82,6 +82,7 @@
import org.eclipse.jgit.lfs.server.LargeFileRepository; import org.eclipse.jgit.lfs.server.LargeFileRepository;
import org.eclipse.jgit.lfs.server.LfsProtocolServlet; import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils; import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.junit.After; import org.junit.After;
@ -119,6 +120,11 @@ public Path getDir() {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
tmp = Files.createTempDirectory("jgit_test_"); tmp = Files.createTempDirectory("jgit_test_");
// measure timer resolution before the test to avoid time critical tests
// are affected by time needed for measurement
FS.getFileStoreAttributes(tmp.getParent());
server = new AppServer(); server = new AppServer();
ServletContextHandler app = server.addContext("/lfs"); ServletContextHandler app = server.addContext("/lfs");
dir = Paths.get(tmp.toString(), "lfs"); dir = Paths.get(tmp.toString(), "lfs");

View File

@ -47,10 +47,6 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<prerequisites>
<maven>3.0</maven>
</prerequisites>
<groupId>org.eclipse.jgit</groupId> <groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId> <artifactId>jgit.tycho.parent</artifactId>
<version>5.2.3-SNAPSHOT</version> <version>5.2.3-SNAPSHOT</version>
@ -59,7 +55,7 @@
<name>JGit Tycho Parent</name> <name>JGit Tycho Parent</name>
<properties> <properties>
<tycho-version>1.2.0</tycho-version> <tycho-version>1.3.0</tycho-version>
<tycho-extras-version>${tycho-version}</tycho-extras-version> <tycho-extras-version>${tycho-version}</tycho-extras-version>
<target-platform>jgit-4.6</target-platform> <target-platform>jgit-4.6</target-platform>
</properties> </properties>
@ -231,6 +227,21 @@
<artifactId>tycho-p2-plugin</artifactId> <artifactId>tycho-p2-plugin</artifactId>
<version>${tycho-version}</version> <version>${tycho-version}</version>
</plugin> </plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-publisher-plugin</artifactId>
<version>${tycho-version}</version>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-repository-plugin</artifactId>
<version>${tycho-version}</version>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-packaging-plugin</artifactId>
<version>${tycho-version}</version>
</plugin>
<plugin> <plugin>
<groupId>org.eclipse.tycho.extras</groupId> <groupId>org.eclipse.tycho.extras</groupId>
<artifactId>tycho-pack200a-plugin</artifactId> <artifactId>tycho-pack200a-plugin</artifactId>
@ -251,6 +262,25 @@
<artifactId>build-helper-maven-plugin</artifactId> <artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version> <version>3.0.0</version>
</plugin> </plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
</build> </build>

View File

@ -48,8 +48,10 @@
import static java.lang.Integer.valueOf; import static java.lang.Integer.valueOf;
import java.text.SimpleDateFormat; import java.time.Instant;
import java.util.Date; import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
@ -67,25 +69,27 @@ class ShowDirCache extends TextBuiltin {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
final SimpleDateFormat fmt; final DateTimeFormatter fmt = DateTimeFormatter
fmt = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss.SSS"); //$NON-NLS-1$ .ofPattern("yyyy-MM-dd,HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
final DirCache cache = db.readDirCache(); final DirCache cache = db.readDirCache();
for (int i = 0; i < cache.getEntryCount(); i++) { for (int i = 0; i < cache.getEntryCount(); i++) {
final DirCacheEntry ent = cache.getEntry(i); final DirCacheEntry ent = cache.getEntry(i);
final FileMode mode = FileMode.fromBits(ent.getRawMode()); final FileMode mode = FileMode.fromBits(ent.getRawMode());
final int len = ent.getLength(); final int len = ent.getLength();
long lastModified = ent.getLastModified(); Instant mtime = ent.getLastModifiedInstant();
final Date mtime = new Date(lastModified);
final int stage = ent.getStage(); final int stage = ent.getStage();
outw.print(mode); outw.print(mode);
outw.format(" %6d", valueOf(len)); //$NON-NLS-1$ outw.format(" %6d", valueOf(len)); //$NON-NLS-1$
outw.print(' '); outw.print(' ');
if (millis) if (millis) {
outw.print(lastModified); outw.print(mtime.toEpochMilli());
else } else {
outw.print(fmt.format(mtime)); outw.print(fmt.format(mtime));
}
outw.print(' '); outw.print(' ');
outw.print(ent.getObjectId().name()); outw.print(ent.getObjectId().name());
outw.print(' '); outw.print(' ');

View File

@ -23,7 +23,6 @@ HELPERS = glob(
"revwalk/RevWalkTestCase.java", "revwalk/RevWalkTestCase.java",
"transport/ObjectIdMatcher.java", "transport/ObjectIdMatcher.java",
"transport/SpiTransport.java", "transport/SpiTransport.java",
"treewalk/FileTreeIteratorWithTimeControl.java",
"treewalk/filter/AlwaysCloneTreeFilter.java", "treewalk/filter/AlwaysCloneTreeFilter.java",
"test/resources/SampleDataRepositoryTestCase.java", "test/resources/SampleDataRepositoryTestCase.java",
"util/CPUTimeStopWatch.java", "util/CPUTimeStopWatch.java",
@ -37,7 +36,7 @@ DATA = [
RESOURCES = glob(["resources/**"]) RESOURCES = glob(["resources/**"])
tests(glob( tests(tests = glob(
["tst/**/*.java"], ["tst/**/*.java"],
exclude = HELPERS + DATA, exclude = HELPERS + DATA,
)) ))

View File

@ -11,9 +11,16 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
com.jcraft.jsch;version="[0.1.54,0.2.0)", com.jcraft.jsch;version="[0.1.54,0.2.0)",
net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)", net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)",
org.apache.commons.compress.archivers;version="[1.15.0,2.0)",
org.apache.commons.compress.archivers.tar;version="[1.15.0,2.0)",
org.apache.commons.compress.archivers.zip;version="[1.15.0,2.0)",
org.apache.commons.compress.compressors.bzip2;version="[1.15.0,2.0)",
org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)",
org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)", org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
org.eclipse.jgit.api;version="[5.2.3,5.3.0)", org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)", org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
org.eclipse.jgit.archive;version="[5.2.3,5.3.0)",
org.eclipse.jgit.attributes;version="[5.2.3,5.3.0)", org.eclipse.jgit.attributes;version="[5.2.3,5.3.0)",
org.eclipse.jgit.awtui;version="[5.2.3,5.3.0)", org.eclipse.jgit.awtui;version="[5.2.3,5.3.0)",
org.eclipse.jgit.blame;version="[5.2.3,5.3.0)", org.eclipse.jgit.blame;version="[5.2.3,5.3.0)",
@ -37,6 +44,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
org.eclipse.jgit.internal.transport.parser;version="[5.2.3,5.3.0)", org.eclipse.jgit.internal.transport.parser;version="[5.2.3,5.3.0)",
org.eclipse.jgit.junit;version="[5.2.3,5.3.0)", org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
org.eclipse.jgit.junit.ssh;version="[5.2.3,5.3.0)", org.eclipse.jgit.junit.ssh;version="[5.2.3,5.3.0)",
org.eclipse.jgit.junit.time;version="[5.2.3,5.3.0)",
org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)", org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)",
org.eclipse.jgit.lib;version="[5.2.3,5.3.0)", org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
org.eclipse.jgit.merge;version="[5.2.3,5.3.0)", org.eclipse.jgit.merge;version="[5.2.3,5.3.0)",
@ -69,7 +77,8 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
org.mockito.junit;version="[2.13.0,3.0.0)", org.mockito.junit;version="[2.13.0,3.0.0)",
org.mockito.stubbing;version="[2.13.0,3.0.0)", org.mockito.stubbing;version="[2.13.0,3.0.0)",
org.objenesis;version="[2.6.0,3.0.0)", org.objenesis;version="[2.6.0,3.0.0)",
org.slf4j;version="[1.7.0,2.0.0)" org.slf4j;version="[1.7.0,2.0.0)",
org.tukaani.xz;version="[1.6.0,2.0)"
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"
Export-Package: org.eclipse.jgit.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache.test" Export-Package: org.eclipse.jgit.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache.test"

View File

@ -122,6 +122,12 @@
<artifactId>org.eclipse.jgit.pgm</artifactId> <artifactId>org.eclipse.jgit.pgm</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>
@ -133,6 +139,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin-version}</version>
<configuration> <configuration>
<argLine>-Djgit.test.long=true</argLine> <argLine>-Djgit.test.long=true</argLine>
</configuration> </configuration>

View File

@ -7,15 +7,16 @@ def tests(tests):
for src in tests: for src in tests:
name = src[len("tst/"):len(src) - len(".java")].replace("/", "_") name = src[len("tst/"):len(src) - len(".java")].replace("/", "_")
labels = [] labels = []
timeout = "moderate"
if name.startswith("org_eclipse_jgit_"): if name.startswith("org_eclipse_jgit_"):
l = name[len("org.eclipse.jgit_"):] package = name[len("org.eclipse.jgit_"):]
if l.startswith("internal_storage_"): if package.startswith("internal_storage_"):
l = l[len("internal.storage_"):] package = package[len("internal.storage_"):]
i = l.find("_") index = package.find("_")
if i > 0: if index > 0:
labels.append(l[:i]) labels.append(package[:index])
else: else:
labels.append(i) labels.append(index)
if "lib" not in labels: if "lib" not in labels:
labels.append("lib") labels.append("lib")
@ -53,9 +54,17 @@ def tests(tests):
additional_deps = [ additional_deps = [
"//lib:mockito", "//lib:mockito",
] ]
if src.endswith("ArchiveCommandTest.java"):
additional_deps = [
"//lib:commons-compress",
"//lib:xz",
"//org.eclipse.jgit.archive:jgit-archive",
]
heap_size = "-Xmx256m" heap_size = "-Xmx256m"
if src.endswith("HugeCommitMessageTest.java"): if src.endswith("HugeCommitMessageTest.java"):
heap_size = "-Xmx512m" heap_size = "-Xmx512m"
if src.endswith("EolRepositoryTest.java") or src.endswith("GcCommitSelectionTest.java"):
timeout = "long"
junit_tests( junit_tests(
name = name, name = name,
@ -73,4 +82,5 @@ def tests(tests):
], ],
flaky = flaky, flaky = flaky,
jvm_flags = [heap_size, "-Dfile.encoding=UTF-8"], jvm_flags = [heap_size, "-Dfile.encoding=UTF-8"],
timeout = timeout,
) )

View File

@ -7,3 +7,8 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
#log4j.appender.fileLogger.bufferedIO = true
#log4j.appender.fileLogger.bufferSize = 4096
#log4j.logger.org.eclipse.jgit.util.FS = DEBUG
#log4j.logger.org.eclipse.jgit.internal.storage.file.FileSnapshot = DEBUG

View File

@ -1268,7 +1268,7 @@ private static DirCacheEntry addEntryToBuilder(String path, File file,
DirCacheEntry entry = new DirCacheEntry(path, stage); DirCacheEntry entry = new DirCacheEntry(path, stage);
entry.setObjectId(id); entry.setObjectId(id);
entry.setFileMode(FileMode.REGULAR_FILE); entry.setFileMode(FileMode.REGULAR_FILE);
entry.setLastModified(file.lastModified()); entry.setLastModified(FS.DETECTED.lastModifiedInstant(file));
entry.setLength((int) file.length()); entry.setLength((int) file.length());
builder.add(entry); builder.add(entry);

View File

@ -47,20 +47,44 @@
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import java.beans.Statement; import java.beans.Statement;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.archive.ArchiveFormats;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.StringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -68,9 +92,14 @@
public class ArchiveCommandTest extends RepositoryTestCase { public class ArchiveCommandTest extends RepositoryTestCase {
// archives store timestamp with 1 second resolution
private static final int WAIT = 2000;
private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size"; private static final String UNEXPECTED_ARCHIVE_SIZE = "Unexpected archive size";
private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents"; private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents";
private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents"; private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents";
private static final String UNEXPECTED_LAST_MODIFIED =
"Unexpected lastModified mocked by MockSystemReader, truncated to 1 second";
private static final String UNEXPECTED_DIFFERENT_HASH = "Unexpected different hash";
private MockFormat format = null; private MockFormat format = null;
@ -78,25 +107,20 @@ public class ArchiveCommandTest extends RepositoryTestCase {
public void setup() { public void setup() {
format = new MockFormat(); format = new MockFormat();
ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format); ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format);
ArchiveFormats.registerAll();
} }
@Override @Override
@After @After
public void tearDown() { public void tearDown() {
ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0)); ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0));
ArchiveFormats.unregisterAll();
} }
@Test @Test
public void archiveHeadAllFiles() throws IOException, GitAPIException { public void archiveHeadAllFiles() throws IOException, GitAPIException {
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
writeTrashFile("file_1.txt", "content_1_1"); createTestContent(git);
git.add().addFilepattern("file_1.txt").call();
git.commit().setMessage("create file").call();
writeTrashFile("file_1.txt", "content_1_2");
writeTrashFile("file_2.txt", "content_2_2");
git.add().addFilepattern(".").call();
git.commit().setMessage("updated file").call();
git.archive().setOutputStream(new MockOutputStream()) git.archive().setOutputStream(new MockOutputStream())
.setFormat(format.SUFFIXES.get(0)) .setFormat(format.SUFFIXES.get(0))
@ -191,6 +215,157 @@ public void archiveByDirectoryPath() throws GitAPIException, IOException {
} }
} }
@Test
public void archiveHeadAllFilesTarTimestamps() throws Exception {
try (Git git = new Git(db)) {
createTestContent(git);
String fmt = "tar";
File archive = new File(getTemporaryDirectory(),
"archive." + format);
archive(git, archive, fmt);
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
try (InputStream fi = Files.newInputStream(archive.toPath());
InputStream bi = new BufferedInputStream(fi);
ArchiveInputStream o = new TarArchiveInputStream(bi)) {
assertEntries(o);
}
Thread.sleep(WAIT);
archive(git, archive, fmt);
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
ObjectId.fromRaw(IO.readFully(archive)));
}
}
@Test
public void archiveHeadAllFilesTgzTimestamps() throws Exception {
try (Git git = new Git(db)) {
createTestContent(git);
String fmt = "tgz";
File archive = new File(getTemporaryDirectory(),
"archive." + fmt);
archive(git, archive, fmt);
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
try (InputStream fi = Files.newInputStream(archive.toPath());
InputStream bi = new BufferedInputStream(fi);
InputStream gzi = new GzipCompressorInputStream(bi);
ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
assertEntries(o);
}
Thread.sleep(WAIT);
archive(git, archive, fmt);
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
ObjectId.fromRaw(IO.readFully(archive)));
}
}
@Test
public void archiveHeadAllFilesTbz2Timestamps() throws Exception {
try (Git git = new Git(db)) {
createTestContent(git);
String fmt = "tbz2";
File archive = new File(getTemporaryDirectory(),
"archive." + fmt);
archive(git, archive, fmt);
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
try (InputStream fi = Files.newInputStream(archive.toPath());
InputStream bi = new BufferedInputStream(fi);
InputStream gzi = new BZip2CompressorInputStream(bi);
ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
assertEntries(o);
}
Thread.sleep(WAIT);
archive(git, archive, fmt);
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
ObjectId.fromRaw(IO.readFully(archive)));
}
}
@Test
public void archiveHeadAllFilesTxzTimestamps() throws Exception {
try (Git git = new Git(db)) {
createTestContent(git);
String fmt = "txz";
File archive = new File(getTemporaryDirectory(), "archive." + fmt);
archive(git, archive, fmt);
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
try (InputStream fi = Files.newInputStream(archive.toPath());
InputStream bi = new BufferedInputStream(fi);
InputStream gzi = new XZCompressorInputStream(bi);
ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
assertEntries(o);
}
Thread.sleep(WAIT);
archive(git, archive, fmt);
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
ObjectId.fromRaw(IO.readFully(archive)));
}
}
@Test
public void archiveHeadAllFilesZipTimestamps() throws Exception {
try (Git git = new Git(db)) {
createTestContent(git);
String fmt = "zip";
File archive = new File(getTemporaryDirectory(), "archive." + fmt);
archive(git, archive, fmt);
ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
try (InputStream fi = Files.newInputStream(archive.toPath());
InputStream bi = new BufferedInputStream(fi);
ArchiveInputStream o = new ZipArchiveInputStream(bi)) {
assertEntries(o);
}
Thread.sleep(WAIT);
archive(git, archive, fmt);
assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
ObjectId.fromRaw(IO.readFully(archive)));
}
}
private void createTestContent(Git git) throws IOException, GitAPIException,
NoFilepatternException, NoHeadException, NoMessageException,
UnmergedPathsException, ConcurrentRefUpdateException,
WrongRepositoryStateException, AbortedByHookException {
writeTrashFile("file_1.txt", "content_1_1");
git.add().addFilepattern("file_1.txt").call();
git.commit().setMessage("create file").call();
writeTrashFile("file_1.txt", "content_1_2");
writeTrashFile("file_2.txt", "content_2_2");
git.add().addFilepattern(".").call();
git.commit().setMessage("updated file").call();
}
private static void archive(Git git, File archive, String fmt)
throws GitAPIException,
FileNotFoundException, AmbiguousObjectException,
IncorrectObjectTypeException, IOException {
git.archive().setOutputStream(new FileOutputStream(archive))
.setFormat(fmt)
.setTree(git.getRepository().resolve("HEAD")).call();
}
private static void assertEntries(ArchiveInputStream o) throws IOException {
ArchiveEntry e;
int n = 0;
while ((e = o.getNextEntry()) != null) {
n++;
assertEquals(UNEXPECTED_LAST_MODIFIED,
(1250379778668L / 1000L) * 1000L,
e.getLastModifiedDate().getTime());
}
assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, n);
}
private static class MockFormat private static class MockFormat
implements ArchiveCommand.Format<MockOutputStream> { implements ArchiveCommand.Format<MockOutputStream> {

View File

@ -43,6 +43,7 @@
*/ */
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.lib.Constants.MASTER; import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
@ -60,6 +61,9 @@
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
@ -74,6 +78,7 @@
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lfs.BuiltinLFS; import org.eclipse.jgit.lfs.BuiltinLFS;
import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -86,6 +91,7 @@
import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.junit.Before; import org.junit.Before;
@ -362,14 +368,14 @@ public void testUpdateSmudgedEntries() throws Exception {
File file = new File(db.getWorkTree(), "Test.txt"); File file = new File(db.getWorkTree(), "Test.txt");
long size = file.length(); long size = file.length();
long mTime = file.lastModified() - 5000L; Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(),
assertTrue(file.setLastModified(mTime)); -5000L);
DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS()); DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS());
DirCacheEntry entry = cache.getEntry("Test.txt"); DirCacheEntry entry = cache.getEntry("Test.txt");
assertNotNull(entry); assertNotNull(entry);
entry.setLength(0); entry.setLength(0);
entry.setLastModified(0); entry.setLastModified(EPOCH);
cache.write(); cache.write();
assertTrue(cache.commit()); assertTrue(cache.commit());
@ -377,10 +383,12 @@ public void testUpdateSmudgedEntries() throws Exception {
entry = cache.getEntry("Test.txt"); entry = cache.getEntry("Test.txt");
assertNotNull(entry); assertNotNull(entry);
assertEquals(0, entry.getLength()); assertEquals(0, entry.getLength());
assertEquals(0, entry.getLastModified()); assertEquals(EPOCH, entry.getLastModifiedInstant());
db.getIndexFile().setLastModified( Files.setLastModifiedTime(db.getIndexFile().toPath(),
db.getIndexFile().lastModified() - 5000); FileTime.from(FS.DETECTED
.lastModifiedInstant(db.getIndexFile())
.minusMillis(5000L)));
assertNotNull(git.checkout().setName("test").call()); assertNotNull(git.checkout().setName("test").call());
@ -388,7 +396,7 @@ public void testUpdateSmudgedEntries() throws Exception {
entry = cache.getEntry("Test.txt"); entry = cache.getEntry("Test.txt");
assertNotNull(entry); assertNotNull(entry);
assertEquals(size, entry.getLength()); assertEquals(size, entry.getLength());
assertEquals(mTime, entry.getLastModified()); assertEquals(mTime, entry.getLastModifiedInstant());
} }
@Test @Test

View File

@ -61,6 +61,7 @@
import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
@ -311,11 +312,11 @@ public void commitSubmoduleUpdate() throws Exception {
public void commitUpdatesSmudgedEntries() throws Exception { public void commitUpdatesSmudgedEntries() throws Exception {
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
File file1 = writeTrashFile("file1.txt", "content1"); File file1 = writeTrashFile("file1.txt", "content1");
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
File file2 = writeTrashFile("file2.txt", "content2"); File file2 = writeTrashFile("file2.txt", "content2");
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
File file3 = writeTrashFile("file3.txt", "content3"); File file3 = writeTrashFile("file3.txt", "content3");
assertTrue(file3.setLastModified(file3.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L);
assertNotNull(git.add().addFilepattern("file1.txt") assertNotNull(git.add().addFilepattern("file1.txt")
.addFilepattern("file2.txt").addFilepattern("file3.txt").call()); .addFilepattern("file2.txt").addFilepattern("file3.txt").call());
@ -346,11 +347,12 @@ public void commitUpdatesSmudgedEntries() throws Exception {
assertEquals(0, cache.getEntry("file2.txt").getLength()); assertEquals(0, cache.getEntry("file2.txt").getLength());
assertEquals(0, cache.getEntry("file3.txt").getLength()); assertEquals(0, cache.getEntry("file3.txt").getLength());
long indexTime = db.getIndexFile().lastModified(); TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
db.getIndexFile().setLastModified(indexTime - 5000); -5000L);
write(file1, "content4"); write(file1, "content4");
assertTrue(file1.setLastModified(file1.lastModified() + 2500));
TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L);
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
.call()); .call());
@ -368,9 +370,9 @@ public void commitUpdatesSmudgedEntries() throws Exception {
public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
File file1 = writeTrashFile("file1.txt", "content1"); File file1 = writeTrashFile("file1.txt", "content1");
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
File file2 = writeTrashFile("file2.txt", "content2"); File file2 = writeTrashFile("file2.txt", "content2");
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
assertNotNull(git.add().addFilepattern("file1.txt") assertNotNull(git.add().addFilepattern("file1.txt")
.addFilepattern("file2.txt").call()); .addFilepattern("file2.txt").call());
@ -399,11 +401,11 @@ public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
assertEquals(0, cache.getEntry("file1.txt").getLength()); assertEquals(0, cache.getEntry("file1.txt").getLength());
assertEquals(0, cache.getEntry("file2.txt").getLength()); assertEquals(0, cache.getEntry("file2.txt").getLength());
long indexTime = db.getIndexFile().lastModified(); TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
db.getIndexFile().setLastModified(indexTime - 5000); -5000L);
write(file1, "content5"); write(file1, "content5");
assertTrue(file1.setLastModified(file1.lastModified() + 1000)); TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L);
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
.call()); .call());

View File

@ -44,7 +44,6 @@
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -55,6 +54,7 @@
import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
@ -230,7 +230,7 @@ public void testDiffWithNegativeLineCount() throws Exception {
@Test @Test
public void testNoOutputStreamSet() throws Exception { public void testNoOutputStreamSet() throws Exception {
File file = writeTrashFile("test.txt", "a"); File file = writeTrashFile("test.txt", "a");
assertTrue(file.setLastModified(file.lastModified() - 5000)); TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L);
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
write(file, "b"); write(file, "b");

View File

@ -42,6 +42,7 @@
*/ */
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD; import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -52,6 +53,9 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
@ -252,13 +256,13 @@ public void testMixedReset() throws JGitInternalException,
public void testMixedResetRetainsSizeAndModifiedTime() throws Exception { public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
git = new Git(db); git = new Git(db);
writeTrashFile("a.txt", "a").setLastModified( Files.setLastModifiedTime(writeTrashFile("a.txt", "a").toPath(),
System.currentTimeMillis() - 60 * 1000); FileTime.from(Instant.now().minusSeconds(60)));
assertNotNull(git.add().addFilepattern("a.txt").call()); assertNotNull(git.add().addFilepattern("a.txt").call());
assertNotNull(git.commit().setMessage("a commit").call()); assertNotNull(git.commit().setMessage("a commit").call());
writeTrashFile("b.txt", "b").setLastModified( Files.setLastModifiedTime(writeTrashFile("b.txt", "b").toPath(),
System.currentTimeMillis() - 60 * 1000); FileTime.from(Instant.now().minusSeconds(60)));
assertNotNull(git.add().addFilepattern("b.txt").call()); assertNotNull(git.add().addFilepattern("b.txt").call());
RevCommit commit2 = git.commit().setMessage("b commit").call(); RevCommit commit2 = git.commit().setMessage("b commit").call();
assertNotNull(commit2); assertNotNull(commit2);
@ -268,12 +272,12 @@ public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
DirCacheEntry aEntry = cache.getEntry("a.txt"); DirCacheEntry aEntry = cache.getEntry("a.txt");
assertNotNull(aEntry); assertNotNull(aEntry);
assertTrue(aEntry.getLength() > 0); assertTrue(aEntry.getLength() > 0);
assertTrue(aEntry.getLastModified() > 0); assertTrue(aEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
DirCacheEntry bEntry = cache.getEntry("b.txt"); DirCacheEntry bEntry = cache.getEntry("b.txt");
assertNotNull(bEntry); assertNotNull(bEntry);
assertTrue(bEntry.getLength() > 0); assertTrue(bEntry.getLength() > 0);
assertTrue(bEntry.getLastModified() > 0); assertTrue(bEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
assertSameAsHead(git.reset().setMode(ResetType.MIXED) assertSameAsHead(git.reset().setMode(ResetType.MIXED)
.setRef(commit2.getName()).call()); .setRef(commit2.getName()).call());
@ -282,13 +286,17 @@ public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
DirCacheEntry mixedAEntry = cache.getEntry("a.txt"); DirCacheEntry mixedAEntry = cache.getEntry("a.txt");
assertNotNull(mixedAEntry); assertNotNull(mixedAEntry);
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); assertEquals(aEntry.getLastModifiedInstant(),
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); mixedAEntry.getLastModifiedInstant());
assertEquals(aEntry.getLastModifiedInstant(),
mixedAEntry.getLastModifiedInstant());
DirCacheEntry mixedBEntry = cache.getEntry("b.txt"); DirCacheEntry mixedBEntry = cache.getEntry("b.txt");
assertNotNull(mixedBEntry); assertNotNull(mixedBEntry);
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); assertEquals(bEntry.getLastModifiedInstant(),
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); mixedBEntry.getLastModifiedInstant());
assertEquals(bEntry.getLastModifiedInstant(),
mixedBEntry.getLastModifiedInstant());
} }
@Test @Test

View File

@ -53,6 +53,7 @@
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.time.Instant;
import org.eclipse.jgit.events.IndexChangedEvent; import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.events.IndexChangedListener; import org.eclipse.jgit.events.IndexChangedListener;
@ -99,7 +100,7 @@ public void testBuildRejectsUnsetFileMode() throws Exception {
public void testBuildOneFile_FinishWriteCommit() throws Exception { public void testBuildOneFile_FinishWriteCommit() throws Exception {
final String path = "a-file-path"; final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE; final FileMode mode = FileMode.REGULAR_FILE;
final long lastModified = 1218123387057L; final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342; final int length = 1342;
final DirCacheEntry entOrig; final DirCacheEntry entOrig;
{ {
@ -117,7 +118,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode()); assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage()); assertEquals(0, entOrig.getStage());
assertEquals(lastModified, entOrig.getLastModified()); assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength()); assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid()); assertFalse(entOrig.isAssumeValid());
b.add(entOrig); b.add(entOrig);
@ -139,7 +140,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode()); assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage()); assertEquals(0, entOrig.getStage());
assertEquals(lastModified, entOrig.getLastModified()); assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength()); assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid()); assertFalse(entOrig.isAssumeValid());
} }
@ -149,7 +150,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
public void testBuildOneFile_Commit() throws Exception { public void testBuildOneFile_Commit() throws Exception {
final String path = "a-file-path"; final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE; final FileMode mode = FileMode.REGULAR_FILE;
final long lastModified = 1218123387057L; final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342; final int length = 1342;
final DirCacheEntry entOrig; final DirCacheEntry entOrig;
{ {
@ -167,7 +168,7 @@ public void testBuildOneFile_Commit() throws Exception {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode()); assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage()); assertEquals(0, entOrig.getStage());
assertEquals(lastModified, entOrig.getLastModified()); assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength()); assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid()); assertFalse(entOrig.isAssumeValid());
b.add(entOrig); b.add(entOrig);
@ -187,7 +188,7 @@ public void testBuildOneFile_Commit() throws Exception {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode()); assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage()); assertEquals(0, entOrig.getStage());
assertEquals(lastModified, entOrig.getLastModified()); assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength()); assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid()); assertFalse(entOrig.isAssumeValid());
} }
@ -204,7 +205,7 @@ final class ReceivedEventMarkerException extends RuntimeException {
final String path = "a-file-path"; final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE; final FileMode mode = FileMode.REGULAR_FILE;
// "old" date in 2008 // "old" date in 2008
final long lastModified = 1218123387057L; final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342; final int length = 1342;
DirCacheEntry entOrig; DirCacheEntry entOrig;
boolean receivedEvent = false; boolean receivedEvent = false;

View File

@ -43,6 +43,7 @@
package org.eclipse.jgit.dircache; package org.eclipse.jgit.dircache;
import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
@ -188,7 +189,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
e.setAssumeValid(false); e.setAssumeValid(false);
e.setCreationTime(2L); e.setCreationTime(2L);
e.setFileMode(FileMode.EXECUTABLE_FILE); e.setFileMode(FileMode.EXECUTABLE_FILE);
e.setLastModified(3L); e.setLastModified(EPOCH.plusMillis(3L));
e.setLength(100L); e.setLength(100L);
e.setObjectId(ObjectId e.setObjectId(ObjectId
.fromString("0123456789012345678901234567890123456789")); .fromString("0123456789012345678901234567890123456789"));
@ -199,7 +200,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
f.setAssumeValid(true); f.setAssumeValid(true);
f.setCreationTime(10L); f.setCreationTime(10L);
f.setFileMode(FileMode.SYMLINK); f.setFileMode(FileMode.SYMLINK);
f.setLastModified(20L); f.setLastModified(EPOCH.plusMillis(20L));
f.setLength(100000000L); f.setLength(100000000L);
f.setObjectId(ObjectId f.setObjectId(ObjectId
.fromString("1234567890123456789012345678901234567890")); .fromString("1234567890123456789012345678901234567890"));
@ -212,7 +213,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
ObjectId.fromString("1234567890123456789012345678901234567890"), ObjectId.fromString("1234567890123456789012345678901234567890"),
e.getObjectId()); e.getObjectId());
assertEquals(FileMode.SYMLINK, e.getFileMode()); assertEquals(FileMode.SYMLINK, e.getFileMode());
assertEquals(20L, e.getLastModified()); assertEquals(EPOCH.plusMillis(20L), e.getLastModifiedInstant());
assertEquals(100000000L, e.getLength()); assertEquals(100000000L, e.getLength());
if (keepStage) if (keepStage)
assertEquals(DirCacheEntry.STAGE_2, e.getStage()); assertEquals(DirCacheEntry.STAGE_2, e.getStage());

View File

@ -56,6 +56,7 @@
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.time.Instant;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
@ -71,6 +72,7 @@
import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -235,7 +237,8 @@ private File[] pack(Repository src, RevObject... list)
private static void write(File[] files, PackWriter pw) private static void write(File[] files, PackWriter pw)
throws IOException { throws IOException {
final long begin = files[0].getParentFile().lastModified(); final Instant begin = FS.DETECTED
.lastModifiedInstant(files[0].getParentFile());
NullProgressMonitor m = NullProgressMonitor.INSTANCE; NullProgressMonitor m = NullProgressMonitor.INSTANCE;
try (OutputStream out = new BufferedOutputStream( try (OutputStream out = new BufferedOutputStream(
@ -252,7 +255,8 @@ private static void write(File[] files, PackWriter pw)
} }
private static void delete(File[] list) throws IOException { private static void delete(File[] list) throws IOException {
final long begin = list[0].getParentFile().lastModified(); final Instant begin = FS.DETECTED
.lastModifiedInstant(list[0].getParentFile());
for (File f : list) { for (File f : list) {
FileUtils.delete(f); FileUtils.delete(f);
assertFalse(f + " was removed", f.exists()); assertFalse(f + " was removed", f.exists());
@ -260,14 +264,14 @@ private static void delete(File[] list) throws IOException {
touch(begin, list[0].getParentFile()); touch(begin, list[0].getParentFile());
} }
private static void touch(long begin, File dir) { private static void touch(Instant begin, File dir) throws IOException {
while (begin >= dir.lastModified()) { while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) {
try { try {
Thread.sleep(25); Thread.sleep(25);
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
// //
} }
dir.setLastModified(System.currentTimeMillis()); FS.DETECTED.setLastModified(dir.toPath(), Instant.now());
} }
} }

View File

@ -42,49 +42,68 @@
*/ */
package org.eclipse.jgit.internal.storage.file; package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.junit.JGitTestUtil.read;
import static org.eclipse.jgit.junit.JGitTestUtil.write;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.FileStoreAttributes;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.Stats;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.junit.After; import org.junit.After;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FileSnapshotTest { public class FileSnapshotTest {
private static final Logger LOG = LoggerFactory
.getLogger(FileSnapshotTest.class);
private List<File> files = new ArrayList<>(); private Path trash;
private File trash; private FileStoreAttributes fsAttrCache;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
trash = File.createTempFile("tmp_", ""); trash = Files.createTempDirectory("tmp_");
trash.delete(); // measure timer resolution before the test to avoid time critical tests
assertTrue("mkdir " + trash, trash.mkdir()); // are affected by time needed for measurement
fsAttrCache = FS
.getFileStoreAttributes(trash.getParent());
} }
@Before @Before
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); FileUtils.delete(trash.toFile(),
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
} }
private static void waitNextSec(File f) { private static void waitNextTick(Path f) throws IOException {
long initialLastModified = f.lastModified(); Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f);
do { do {
f.setLastModified(System.currentTimeMillis()); FS.DETECTED.setLastModified(f, Instant.now());
} while (f.lastModified() == initialLastModified); } while (FS.DETECTED.lastModifiedInstant(f)
.equals(initialLastModified));
} }
/** /**
@ -94,12 +113,12 @@ private static void waitNextSec(File f) {
*/ */
@Test @Test
public void testActuallyIsModifiedTrivial() throws Exception { public void testActuallyIsModifiedTrivial() throws Exception {
File f1 = createFile("simple"); Path f1 = createFile("simple");
waitNextSec(f1); waitNextTick(f1);
FileSnapshot save = FileSnapshot.save(f1); FileSnapshot save = FileSnapshot.save(f1.toFile());
append(f1, (byte) 'x'); append(f1, (byte) 'x');
waitNextSec(f1); waitNextTick(f1);
assertTrue(save.isModified(f1)); assertTrue(save.isModified(f1.toFile()));
} }
/** /**
@ -112,11 +131,17 @@ public void testActuallyIsModifiedTrivial() throws Exception {
*/ */
@Test @Test
public void testNewFileWithWait() throws Exception { public void testNewFileWithWait() throws Exception {
File f1 = createFile("newfile"); // if filesystem timestamp resolution is high the snapshot won't be
waitNextSec(f1); // racily clean
FileSnapshot save = FileSnapshot.save(f1); Assume.assumeTrue(
Thread.sleep(1500); fsAttrCache.getFsTimestampResolution()
assertTrue(save.isModified(f1)); .compareTo(Duration.ofMillis(10)) > 0);
Path f1 = createFile("newfile");
waitNextTick(f1);
FileSnapshot save = FileSnapshot.save(f1.toFile());
TimeUnit.NANOSECONDS.sleep(
fsAttrCache.getFsTimestampResolution().dividedBy(2).toNanos());
assertTrue(save.isModified(f1.toFile()));
} }
/** /**
@ -126,9 +151,33 @@ public void testNewFileWithWait() throws Exception {
*/ */
@Test @Test
public void testNewFileNoWait() throws Exception { public void testNewFileNoWait() throws Exception {
File f1 = createFile("newfile"); // if filesystem timestamp resolution is smaller than time needed to
FileSnapshot save = FileSnapshot.save(f1); // create a file and FileSnapshot the snapshot won't be racily clean
assertTrue(save.isModified(f1)); Assume.assumeTrue(fsAttrCache.getFsTimestampResolution()
.compareTo(Duration.ofMillis(10)) > 0);
for (int i = 0; i < 50; i++) {
Instant start = Instant.now();
Path f1 = createFile("newfile");
FileSnapshot save = FileSnapshot.save(f1.toFile());
Duration res = FS.getFileStoreAttributes(f1)
.getFsTimestampResolution();
Instant end = Instant.now();
if (Duration.between(start, end)
.compareTo(res.multipliedBy(2)) > 0) {
// This test is racy: under load, there may be a delay between createFile() and
// FileSnapshot.save(). This can stretch the time between the read TS and FS
// creation TS to the point that it exceeds the FS granularity, and we
// conclude it cannot be racily clean, and therefore must be really clean.
//
// This should be relatively uncommon.
continue;
}
// The file wasn't really modified, but it looks just like a "maybe racily clean"
// file.
assertTrue(save.isModified(f1.toFile()));
return;
}
fail("too much load for this test");
} }
/** /**
@ -142,19 +191,19 @@ public void testNewFileNoWait() throws Exception {
@Test @Test
public void testSimulatePackfileReplacement() throws Exception { public void testSimulatePackfileReplacement() throws Exception {
Assume.assumeFalse(SystemReader.getInstance().isWindows()); Assume.assumeFalse(SystemReader.getInstance().isWindows());
File f1 = createFile("file"); // inode y Path f1 = createFile("file"); // inode y
File f2 = createFile("fool"); // Guarantees new inode x Path f2 = createFile("fool"); // Guarantees new inode x
// wait on f2 since this method resets lastModified of the file // wait on f2 since this method resets lastModified of the file
// and leaves lastModified of f1 untouched // and leaves lastModified of f1 untouched
waitNextSec(f2); waitNextTick(f2);
waitNextSec(f2); waitNextTick(f2);
FileTime timestamp = Files.getLastModifiedTime(f1.toPath()); FileTime timestamp = Files.getLastModifiedTime(f1);
FileSnapshot save = FileSnapshot.save(f1); FileSnapshot save = FileSnapshot.save(f1.toFile());
Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x Files.move(f2, f1, // Now "file" is inode x
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE); StandardCopyOption.ATOMIC_MOVE);
Files.setLastModifiedTime(f1.toPath(), timestamp); Files.setLastModifiedTime(f1, timestamp);
assertTrue(save.isModified(f1)); assertTrue(save.isModified(f1.toFile()));
assertTrue("unexpected change of fileKey", save.wasFileKeyChanged()); assertTrue("unexpected change of fileKey", save.wasFileKeyChanged());
assertFalse("unexpected size change", save.wasSizeChanged()); assertFalse("unexpected size change", save.wasSizeChanged());
assertFalse("unexpected lastModified change", assertFalse("unexpected lastModified change",
@ -171,24 +220,83 @@ public void testSimulatePackfileReplacement() throws Exception {
*/ */
@Test @Test
public void testFileSizeChanged() throws Exception { public void testFileSizeChanged() throws Exception {
File f = createFile("file"); Path f = createFile("file");
FileTime timestamp = Files.getLastModifiedTime(f.toPath()); FileTime timestamp = Files.getLastModifiedTime(f);
FileSnapshot save = FileSnapshot.save(f); FileSnapshot save = FileSnapshot.save(f.toFile());
append(f, (byte) 'x'); append(f, (byte) 'x');
Files.setLastModifiedTime(f.toPath(), timestamp); Files.setLastModifiedTime(f, timestamp);
assertTrue(save.isModified(f)); assertTrue(save.isModified(f.toFile()));
assertTrue(save.wasSizeChanged()); assertTrue(save.wasSizeChanged());
} }
private File createFile(String string) throws IOException { @Test
trash.mkdirs(); public void fileSnapshotEquals() throws Exception {
File f = File.createTempFile(string, "tdat", trash); // 0 sized FileSnapshot.
files.add(f); FileSnapshot fs1 = FileSnapshot.MISSING_FILE;
return f; // UNKNOWN_SIZE FileSnapshot.
FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant());
assertTrue(fs1.equals(fs2));
assertTrue(fs2.equals(fs1));
} }
private static void append(File f, byte b) throws IOException { @SuppressWarnings("boxing")
try (FileOutputStream os = new FileOutputStream(f, true)) { @Test
public void detectFileModified() throws IOException {
int failures = 0;
long racyNanos = 0;
final int COUNT = 10000;
ArrayList<Long> deltas = new ArrayList<>();
File f = createFile("test").toFile();
for (int i = 0; i < COUNT; i++) {
write(f, "a");
FileSnapshot snapshot = FileSnapshot.save(f);
assertEquals("file should contain 'a'", "a", read(f));
write(f, "b");
if (!snapshot.isModified(f)) {
deltas.add(snapshot.lastDelta());
racyNanos = snapshot.lastRacyThreshold();
failures++;
}
assertEquals("file should contain 'b'", "b", read(f));
}
if (failures > 0) {
Stats stats = new Stats();
LOG.debug(
"delta [ns] since modification FileSnapshot failed to detect");
for (Long d : deltas) {
stats.add(d);
LOG.debug(String.format("%,d", d));
}
LOG.error(
"count, failures, eff. racy threshold [ns], delta min [ns],"
+ " delta max [ns], delta avg [ns],"
+ " delta stddev [ns]");
LOG.error(String.format(
"%,d, %,d, %,d, %,.0f, %,.0f, %,.0f, %,.0f", COUNT,
failures, racyNanos, stats.min(), stats.max(),
stats.avg(), stats.stddev()));
}
assertTrue(
String.format(
"FileSnapshot: failures to detect file modifications"
+ " %d out of %d\n"
+ "timestamp resolution %d µs"
+ " min racy threshold %d µs"
, failures, COUNT,
fsAttrCache.getFsTimestampResolution().toNanos() / 1000,
fsAttrCache.getMinimalRacyInterval().toNanos() / 1000),
failures == 0);
}
private Path createFile(String string) throws IOException {
Files.createDirectories(trash);
return Files.createTempFile(trash, string, "tdat");
}
private static void append(Path f, byte b) throws IOException {
try (OutputStream os = Files.newOutputStream(f,
StandardOpenOption.APPEND)) {
os.write(b); os.write(b);
} }
} }

View File

@ -147,9 +147,10 @@ protected RevCommit commitChain(int depth, int width) throws Exception {
return tip; return tip;
} }
protected long lastModified(AnyObjectId objectId) throws IOException { protected long lastModified(AnyObjectId objectId) {
return repo.getFS().lastModified( return repo.getFS()
repo.getObjectDatabase().fileFor(objectId)); .lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId))
.toEpochMilli();
} }
protected static void fsTick() throws InterruptedException, IOException { protected static void fsTick() throws InterruptedException, IOException {

View File

@ -66,6 +66,7 @@
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -158,13 +159,14 @@ public void testScanningForPackfiles() throws Exception {
// To deal with racy-git situations JGit's Filesnapshot class will // To deal with racy-git situations JGit's Filesnapshot class will
// report a file/folder potentially dirty if // report a file/folder potentially dirty if
// cachedLastReadTime-cachedLastModificationTime < 2500ms. This // cachedLastReadTime-cachedLastModificationTime < filesystem
// causes JGit to always rescan a file after modification. But: // timestamp resolution. This causes JGit to always rescan a file
// this was true only if the difference between current system time // after modification. But: this was true only if the difference
// and cachedLastModification time was less than 2500ms. If the // between current system time and cachedLastModification time was
// modification is more than 2500ms ago we may have reported a // less than 2500ms. If the modification is more than 2500ms ago we
// file/folder to be clean although it has not been rescanned. A // may have reported a file/folder to be clean although it has not
// Bug. To show the bug we sleep for more than 2500ms // been rescanned. A bug. To show the bug we sleep for more than
// 2500ms
Thread.sleep(2600); Thread.sleep(2600);
File[] ret = packsFolder.listFiles(new FilenameFilter() { File[] ret = packsFolder.listFiles(new FilenameFilter() {
@ -174,7 +176,9 @@ public boolean accept(File dir, String name) {
} }
}); });
assertTrue(ret != null && ret.length == 1); assertTrue(ret != null && ret.length == 1);
Assume.assumeTrue(tmpFile.lastModified() == ret[0].lastModified()); FS fs = db.getFS();
Assume.assumeTrue(fs.lastModifiedInstant(tmpFile)
.equals(fs.lastModifiedInstant(ret[0])));
// all objects are in a new packfile but we will not detect it // all objects are in a new packfile but we will not detect it
assertFalse(receivingDB.hasObject(unknownID)); assertFalse(receivingDB.hasObject(unknownID));

View File

@ -59,6 +59,7 @@
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
//import java.nio.file.attribute.BasicFileAttributes; //import java.nio.file.attribute.BasicFileAttributes;
import java.text.ParseException; import java.text.ParseException;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Random; import java.util.Random;
@ -80,6 +81,7 @@
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.FS;
import org.junit.Test; import org.junit.Test;
public class PackFileSnapshotTest extends RepositoryTestCase { public class PackFileSnapshotTest extends RepositoryTestCase {
@ -188,7 +190,8 @@ public void testDetectModificationAlthoughSameSizeAndModificationtime()
AnyObjectId chk1 = pf.getPackChecksum(); AnyObjectId chk1 = pf.getPackChecksum();
String name = pf.getPackName(); String name = pf.getPackName();
Long length = Long.valueOf(pf.getPackFile().length()); Long length = Long.valueOf(pf.getPackFile().length());
long m1 = packFilePath.toFile().lastModified(); FS fs = db.getFS();
Instant m1 = fs.lastModifiedInstant(packFilePath);
// Wait for a filesystem timer tick to enhance probability the rest of // Wait for a filesystem timer tick to enhance probability the rest of
// this test is done before the filesystem timer ticks again. // this test is done before the filesystem timer ticks again.
@ -198,15 +201,15 @@ public void testDetectModificationAlthoughSameSizeAndModificationtime()
// content and checksum are different since compression level differs // content and checksum are different since compression level differs
AnyObjectId chk2 = repackAndCheck(6, name, length, chk1) AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
.getPackChecksum(); .getPackChecksum();
long m2 = packFilePath.toFile().lastModified(); Instant m2 = fs.lastModifiedInstant(packFilePath);
assumeFalse(m2 == m1); assumeFalse(m2.equals(m1));
// Repack to create packfile with same name, length. Lastmodified is // Repack to create packfile with same name, length. Lastmodified is
// equal to the previous one because we are in the same filesystem timer // equal to the previous one because we are in the same filesystem timer
// slot. Content and its checksum are different // slot. Content and its checksum are different
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
.getPackChecksum(); .getPackChecksum();
long m3 = packFilePath.toFile().lastModified(); Instant m3 = fs.lastModifiedInstant(packFilePath);
// ask for an unknown git object to force jgit to rescan the list of // ask for an unknown git object to force jgit to rescan the list of
// available packs. If we would ask for a known objectid then JGit would // available packs. If we would ask for a known objectid then JGit would
@ -214,7 +217,7 @@ public void testDetectModificationAlthoughSameSizeAndModificationtime()
db.getObjectDatabase().has(unknownID); db.getObjectDatabase().has(unknownID);
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum()); .getPackChecksum());
assumeTrue(m3 == m2); assumeTrue(m3.equals(m2));
} }
// Try repacking so fast that we get two new packs which differ only in // Try repacking so fast that we get two new packs which differ only in
@ -253,7 +256,8 @@ public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey(
// Repack to create third packfile // Repack to create third packfile
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
.getPackChecksum(); .getPackChecksum();
long m3 = packFilePath.toFile().lastModified(); FS fs = db.getFS();
Instant m3 = fs.lastModifiedInstant(packFilePath);
db.getObjectDatabase().has(unknownID); db.getObjectDatabase().has(unknownID);
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum()); .getPackChecksum());
@ -265,8 +269,8 @@ public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey(
// Copy copy2 to packfile data to force modification of packfile without // Copy copy2 to packfile data to force modification of packfile without
// changing the packfile's filekey. // changing the packfile's filekey.
copyPack(packFileBasePath, ".copy2", ""); copyPack(packFileBasePath, ".copy2", "");
long m2 = packFilePath.toFile().lastModified(); Instant m2 = fs.lastModifiedInstant(packFilePath);
assumeFalse(m3 == m2); assumeFalse(m3.equals(m2));
db.getObjectDatabase().has(unknownID); db.getObjectDatabase().has(unknownID);
assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks()) assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks())
@ -275,8 +279,8 @@ public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey(
// Copy copy2 to packfile data to force modification of packfile without // Copy copy2 to packfile data to force modification of packfile without
// changing the packfile's filekey. // changing the packfile's filekey.
copyPack(packFileBasePath, ".copy1", ""); copyPack(packFileBasePath, ".copy1", "");
long m1 = packFilePath.toFile().lastModified(); Instant m1 = fs.lastModifiedInstant(packFilePath);
assumeTrue(m2 == m1); assumeTrue(m2.equals(m1));
db.getObjectDatabase().has(unknownID); db.getObjectDatabase().has(unknownID);
assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks()) assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum()); .getPackChecksum());

View File

@ -59,6 +59,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
@ -70,6 +71,7 @@
import org.eclipse.jgit.events.RefsChangedEvent; import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener; import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.Repeat;
import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
@ -78,6 +80,7 @@
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.util.FS;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -642,6 +645,7 @@ public void testGetRefs_DiscoversModifiedLoose() throws IOException {
assertEquals(B, all.get(HEAD).getObjectId()); assertEquals(B, all.get(HEAD).getObjectId());
} }
@Repeat(n = 100, abortOnFailure = false)
@Test @Test
public void testGetRef_DiscoversModifiedLoose() throws IOException { public void testGetRef_DiscoversModifiedLoose() throws IOException {
Map<String, Ref> all; Map<String, Ref> all;
@ -1319,10 +1323,8 @@ private void writePackedRef(String name, AnyObjectId id) throws IOException {
private void writePackedRefs(String content) throws IOException { private void writePackedRefs(String content) throws IOException {
File pr = new File(diskRepo.getDirectory(), "packed-refs"); File pr = new File(diskRepo.getDirectory(), "packed-refs");
write(pr, content); write(pr, content);
FS fs = diskRepo.getFS();
final long now = System.currentTimeMillis(); fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600));
final int oneHourAgo = 3600 * 1000;
pr.setLastModified(now - oneHourAgo);
} }
private void deleteLooseRef(String name) { private void deleteLooseRef(String name) {

View File

@ -59,6 +59,7 @@
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.time.Instant;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -82,6 +83,7 @@
import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.junit.Rule; import org.junit.Rule;
@ -790,12 +792,14 @@ private RevTag parseTag(AnyObjectId id) throws MissingObjectException,
* *
* @param name * @param name
* the file in the repository to force a time change on. * the file in the repository to force a time change on.
* @throws IOException
*/ */
private void BUG_WorkAroundRacyGitIssues(String name) { private void BUG_WorkAroundRacyGitIssues(String name) throws IOException {
File path = new File(db.getDirectory(), name); File path = new File(db.getDirectory(), name);
long old = path.lastModified(); FS fs = db.getFS();
Instant old = fs.lastModifiedInstant(path);
long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
path.setLastModified(set); fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set));
assertTrue("time changed", old != path.lastModified()); assertFalse("time changed", old.equals(fs.lastModifiedInstant(path)));
} }
} }

View File

@ -51,8 +51,10 @@
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.jgit.util.FileUtils.pathToString; import static org.eclipse.jgit.util.FileUtils.pathToString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@ -1224,8 +1226,18 @@ private static Config parse(String content, Config baseConfig)
@Test @Test
public void testTimeUnit() throws ConfigInvalidException { public void testTimeUnit() throws ConfigInvalidException {
assertEquals(0, parseTime("0", NANOSECONDS));
assertEquals(2, parseTime("2ns", NANOSECONDS));
assertEquals(200, parseTime("200 nanoseconds", NANOSECONDS));
assertEquals(0, parseTime("0", MICROSECONDS));
assertEquals(2, parseTime("2us", MICROSECONDS));
assertEquals(2, parseTime("2000 nanoseconds", MICROSECONDS));
assertEquals(200, parseTime("200 microseconds", MICROSECONDS));
assertEquals(0, parseTime("0", MILLISECONDS)); assertEquals(0, parseTime("0", MILLISECONDS));
assertEquals(2, parseTime("2ms", MILLISECONDS)); assertEquals(2, parseTime("2ms", MILLISECONDS));
assertEquals(2, parseTime("2000microseconds", MILLISECONDS));
assertEquals(200, parseTime("200 milliseconds", MILLISECONDS)); assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
assertEquals(0, parseTime("0s", SECONDS)); assertEquals(0, parseTime("0s", SECONDS));

View File

@ -37,8 +37,12 @@
*/ */
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.time.Instant;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
@ -63,11 +67,11 @@ public void testLastModifiedTimes() throws Exception {
DirCacheEntry entry = dc.getEntry(path); DirCacheEntry entry = dc.getEntry(path);
DirCacheEntry entry2 = dc.getEntry(path); DirCacheEntry entry2 = dc.getEntry(path);
assertTrue("last modified shall not be zero!", assertFalse("last modified shall not be the epoch!",
entry.getLastModified() != 0); entry.getLastModifiedInstant().equals(EPOCH));
assertTrue("last modified shall not be zero!", assertFalse("last modified shall not be the epoch!",
entry2.getLastModified() != 0); entry2.getLastModifiedInstant().equals(EPOCH));
writeTrashFile(path, "new content"); writeTrashFile(path, "new content");
git.add().addFilepattern(path).call(); git.add().addFilepattern(path).call();
@ -77,11 +81,11 @@ public void testLastModifiedTimes() throws Exception {
entry = dc.getEntry(path); entry = dc.getEntry(path);
entry2 = dc.getEntry(path); entry2 = dc.getEntry(path);
assertTrue("last modified shall not be zero!", assertFalse("last modified shall not be the epoch!",
entry.getLastModified() != 0); entry.getLastModifiedInstant().equals(EPOCH));
assertTrue("last modified shall not be zero!", assertFalse("last modified shall not be the epoch!",
entry2.getLastModified() != 0); entry2.getLastModifiedInstant().equals(EPOCH));
} }
} }
@ -97,7 +101,7 @@ public void testModify() throws Exception {
DirCache dc = db.readDirCache(); DirCache dc = db.readDirCache();
DirCacheEntry entry = dc.getEntry(path); DirCacheEntry entry = dc.getEntry(path);
long masterLastMod = entry.getLastModified(); Instant masterLastMod = entry.getLastModifiedInstant();
git.checkout().setCreateBranch(true).setName("side").call(); git.checkout().setCreateBranch(true).setName("side").call();
@ -110,7 +114,7 @@ public void testModify() throws Exception {
dc = db.readDirCache(); dc = db.readDirCache();
entry = dc.getEntry(path); entry = dc.getEntry(path);
long sideLastMode = entry.getLastModified(); Instant sideLastMod = entry.getLastModifiedInstant();
Thread.sleep(2000); Thread.sleep(2000);
@ -120,9 +124,10 @@ public void testModify() throws Exception {
dc = db.readDirCache(); dc = db.readDirCache();
entry = dc.getEntry(path); entry = dc.getEntry(path);
assertTrue("shall have equal mod time!", masterLastMod == sideLastMode); assertTrue("shall have equal mod time!",
assertTrue("shall not equal master timestamp!", masterLastMod.equals(sideLastMod));
entry.getLastModified() == masterLastMod); assertTrue("shall have equal master timestamp!",
entry.getLastModifiedInstant().equals(masterLastMod));
} }
} }

View File

@ -42,95 +42,29 @@
*/ */
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import static java.lang.Long.valueOf;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.TreeSet; import java.time.Instant;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl; import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.NameConflictTreeWalk; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Test; import org.junit.Test;
public class RacyGitTests extends RepositoryTestCase { public class RacyGitTests extends RepositoryTestCase {
@Test
public void testIterator() throws IllegalStateException, IOException,
InterruptedException {
TreeSet<Long> modTimes = new TreeSet<>();
File lastFile = null;
for (int i = 0; i < 10; i++) {
lastFile = new File(db.getWorkTree(), "0." + i);
FileUtils.createNewFile(lastFile);
if (i == 5)
fsTick(lastFile);
}
modTimes.add(valueOf(fsTick(lastFile)));
for (int i = 0; i < 10; i++) {
lastFile = new File(db.getWorkTree(), "1." + i);
FileUtils.createNewFile(lastFile);
}
modTimes.add(valueOf(fsTick(lastFile)));
for (int i = 0; i < 10; i++) {
lastFile = new File(db.getWorkTree(), "2." + i);
FileUtils.createNewFile(lastFile);
if (i % 4 == 0)
fsTick(lastFile);
}
FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl(
db, modTimes);
try (NameConflictTreeWalk tw = new NameConflictTreeWalk(db)) {
tw.addTree(fileIt);
tw.setRecursive(true);
FileTreeIterator t;
long t0 = 0;
for (int i = 0; i < 10; i++) {
assertTrue(tw.next());
t = tw.getTree(0, FileTreeIterator.class);
if (i == 0) {
t0 = t.getEntryLastModified();
} else {
assertEquals(t0, t.getEntryLastModified());
}
}
long t1 = 0;
for (int i = 0; i < 10; i++) {
assertTrue(tw.next());
t = tw.getTree(0, FileTreeIterator.class);
if (i == 0) {
t1 = t.getEntryLastModified();
assertTrue(t1 > t0);
} else {
assertEquals(t1, t.getEntryLastModified());
}
}
long t2 = 0;
for (int i = 0; i < 10; i++) {
assertTrue(tw.next());
t = tw.getTree(0, FileTreeIterator.class);
if (i == 0) {
t2 = t.getEntryLastModified();
assertTrue(t2 > t1);
} else {
assertEquals(t2, t.getEntryLastModified());
}
}
}
}
@Test @Test
public void testRacyGitDetection() throws Exception { public void testRacyGitDetection() throws Exception {
TreeSet<Long> modTimes = new TreeSet<>();
File lastFile;
// Reset to force creation of index file // Reset to force creation of index file
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
git.reset().call(); git.reset().call();
@ -138,48 +72,60 @@ public void testRacyGitDetection() throws Exception {
// wait to ensure that modtimes of the file doesn't match last index // wait to ensure that modtimes of the file doesn't match last index
// file modtime // file modtime
modTimes.add(valueOf(fsTick(db.getIndexFile()))); fsTick(db.getIndexFile());
// create two files // create two files
addToWorkDir("a", "a"); File a = writeToWorkDir("a", "a");
lastFile = addToWorkDir("b", "b"); File b = writeToWorkDir("b", "b");
TimeUtil.setLastModifiedOf(a.toPath(), b.toPath());
TimeUtil.setLastModifiedOf(b.toPath(), b.toPath());
// wait to ensure that file-modTimes and therefore index entry modTime // wait to ensure that file-modTimes and therefore index entry modTime
// doesn't match the modtime of index-file after next persistance // doesn't match the modtime of index-file after next persistance
modTimes.add(valueOf(fsTick(lastFile))); fsTick(b);
// now add both files to the index. No racy git expected // now add both files to the index. No racy git expected
resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes)); resetIndex(new FileTreeIterator(db));
assertEquals( assertEquals(
"[a, mode:100644, time:t0, length:1, content:a]" + "[a, mode:100644, time:t0, length:1, content:a]"
"[b, mode:100644, time:t0, length:1, content:b]", + "[b, mode:100644, time:t0, length:1, content:b]",
indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT)); indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT));
// Remember the last modTime of index file. All modifications times of // wait to ensure the file 'a' is updated at t1.
// further modification are translated to this value so it looks that fsTick(db.getIndexFile());
// files have been modified in the same time slot as the index file
long indexMod = db.getIndexFile().lastModified();
modTimes.add(Long.valueOf(indexMod));
// modify one file // Create a racy git situation. This is a situation that the index is
long aMod = addToWorkDir("a", "a2").lastModified(); // updated and then a file is modified within the same tick of the
assumeTrue(aMod == indexMod); // filesystem timestamp resolution. By changing the index file
// artificially, we create a fake racy situation.
File updatedA = writeToWorkDir("a", "a2");
Instant newLastModified = TimeUtil
.setLastModifiedWithOffset(updatedA.toPath(), 100L);
resetIndex(new FileTreeIterator(db));
FS.DETECTED.setLastModified(db.getIndexFile().toPath(),
newLastModified);
// now update the index the index. 'a' has to be racily clean -- because DirCache dc = db.readDirCache();
// it's modification time is exactly the same as the previous index file // check index state: although racily clean a should not be reported as
// mod time. // being dirty since we forcefully reset the index to match the working
resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes)); // tree
db.readDirCache();
// although racily clean a should not be reported as being dirty
assertEquals( assertEquals(
"[a, mode:100644, time:t1, smudged, length:0, content:a2]" + "[a, mode:100644, time:t1, smudged, length:0, content:a2]"
"[b, mode:100644, time:t0, length:1, content:b]", + "[b, mode:100644, time:t0, length:1, content:b]",
indexState(SMUDGE|MOD_TIME|LENGTH|CONTENT)); indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT));
// compare state of files in working tree with index to check that
// FileTreeIterator.isModified() works as expected
FileTreeIterator f = new FileTreeIterator(db.getWorkTree(), db.getFS(),
db.getConfig().get(WorkingTreeOptions.KEY));
assertTrue(f.findFile("a"));
try (ObjectReader reader = db.newObjectReader()) {
assertFalse(f.isModified(dc.getEntry("a"), false, reader));
}
} }
private File addToWorkDir(String path, String content) throws IOException { private File writeToWorkDir(String path, String content) throws IOException {
File f = new File(db.getWorkTree(), path); File f = new File(db.getWorkTree(), path);
try (FileOutputStream fos = new FileOutputStream(f)) { try (FileOutputStream fos = new FileOutputStream(f)) {
fos.write(content.getBytes(UTF_8)); fos.write(content.getBytes(UTF_8));

View File

@ -43,6 +43,7 @@
package org.eclipse.jgit.merge; package org.eclipse.jgit.merge;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -54,6 +55,7 @@
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
@ -1090,13 +1092,13 @@ public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
@Theory @Theory
public void checkForCorrectIndex(MergeStrategy strategy) throws Exception { public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
File f; File f;
long lastTs4, lastTsIndex; Instant lastTs4, lastTsIndex;
Git git = Git.wrap(db); Git git = Git.wrap(db);
File indexFile = db.getIndexFile(); File indexFile = db.getIndexFile();
// Create initial content and remember when the last file was written. // Create initial content and remember when the last file was written.
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
lastTs4 = FS.DETECTED.lastModified(f); lastTs4 = FS.DETECTED.lastModifiedInstant(f);
// add all files, commit and check this doesn't update any working tree // add all files, commit and check this doesn't update any working tree
// files and that the index is in a new file system timer tick. Make // files and that the index is in a new file system timer tick. Make
@ -1109,8 +1111,9 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
checkConsistentLastModified("0", "1", "2", "3", "4"); checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
assertEquals("Commit should not touch working tree file 4", lastTs4, assertEquals("Commit should not touch working tree file 4", lastTs4,
FS.DETECTED.lastModified(new File(db.getWorkTree(), "4"))); FS.DETECTED
lastTsIndex = FS.DETECTED.lastModified(indexFile); .lastModifiedInstant(new File(db.getWorkTree(), "4")));
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// Do modifications on the master branch. Then add and commit. This // Do modifications on the master branch. Then add and commit. This
// should touch only "0", "2 and "3" // should touch only "0", "2 and "3"
@ -1124,7 +1127,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
checkConsistentLastModified("0", "1", "2", "3", "4"); checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+ lastTsIndex, "<0", "2", "3", "<.git/index"); + lastTsIndex, "<0", "2", "3", "<.git/index");
lastTsIndex = FS.DETECTED.lastModified(indexFile); lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// Checkout a side branch. This should touch only "0", "2 and "3" // Checkout a side branch. This should touch only "0", "2 and "3"
fsTick(indexFile); fsTick(indexFile);
@ -1133,7 +1136,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
checkConsistentLastModified("0", "1", "2", "3", "4"); checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+ lastTsIndex, "<0", "2", "3", ".git/index"); + lastTsIndex, "<0", "2", "3", ".git/index");
lastTsIndex = FS.DETECTED.lastModified(indexFile); lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// This checkout may have populated worktree and index so fast that we // This checkout may have populated worktree and index so fast that we
// may have smudged entries now. Check that we have the right content // may have smudged entries now. Check that we have the right content
@ -1146,13 +1149,13 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
indexState(CONTENT)); indexState(CONTENT));
fsTick(indexFile); fsTick(indexFile);
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
lastTs4 = FS.DETECTED.lastModified(f); lastTs4 = FS.DETECTED.lastModifiedInstant(f);
fsTick(f); fsTick(f);
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
checkConsistentLastModified("0", "1", "2", "3", "4"); checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
"4", "<.git/index"); "4", "<.git/index");
lastTsIndex = FS.DETECTED.lastModified(indexFile); lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// Do modifications on the side branch. Touch only "1", "2 and "3" // Do modifications on the side branch. Touch only "1", "2 and "3"
fsTick(indexFile); fsTick(indexFile);
@ -1163,7 +1166,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
checkConsistentLastModified("0", "1", "2", "3", "4"); checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
+ lastTsIndex, "<1", "2", "3", "<.git/index"); + lastTsIndex, "<1", "2", "3", "<.git/index");
lastTsIndex = FS.DETECTED.lastModified(indexFile); lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// merge master and side. Should only touch "0," "2" and "3" // merge master and side. Should only touch "0," "2" and "3"
fsTick(indexFile); fsTick(indexFile);
@ -1330,9 +1333,10 @@ private void checkConsistentLastModified(String... pathes)
assertEquals( assertEquals(
"IndexEntry with path " "IndexEntry with path "
+ path + path
+ " has lastmodified with is different from the worktree file", + " has lastmodified which is different from the worktree file",
FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path) FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
.getLastModified()); dc.getEntry(path)
.getLastModifiedInstant());
} }
// Assert that modification timestamps of working tree files are as // Assert that modification timestamps of working tree files are as
@ -1341,21 +1345,22 @@ private void checkConsistentLastModified(String... pathes)
// then this file must be younger then file i. A path "*<modtime>" // then this file must be younger then file i. A path "*<modtime>"
// represents a file with a modification time of <modtime> // represents a file with a modification time of <modtime>
// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
private void checkModificationTimeStampOrder(String... pathes) private void checkModificationTimeStampOrder(String... pathes) {
throws IOException { Instant lastMod = EPOCH;
long lastMod = Long.MIN_VALUE;
for (String p : pathes) { for (String p : pathes) {
boolean strong = p.startsWith("<"); boolean strong = p.startsWith("<");
boolean fixed = p.charAt(strong ? 1 : 0) == '*'; boolean fixed = p.charAt(strong ? 1 : 0) == '*';
p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0)); p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
long curMod = fixed ? Long.valueOf(p).longValue() Instant curMod = fixed ? Instant.parse(p)
: FS.DETECTED.lastModified(new File(db.getWorkTree(), p)); : FS.DETECTED
if (strong) .lastModifiedInstant(new File(db.getWorkTree(), p));
if (strong) {
assertTrue("path " + p + " is not younger than predecesssor", assertTrue("path " + p + " is not younger than predecesssor",
curMod > lastMod); curMod.compareTo(lastMod) > 0);
else } else {
assertTrue("path " + p + " is older than predecesssor", assertTrue("path " + p + " is older than predecesssor",
curMod >= lastMod); curMod.compareTo(lastMod) >= 0);
}
} }
} }

View File

@ -46,12 +46,13 @@
import static org.eclipse.jgit.util.FileUtils.pathToString; import static org.eclipse.jgit.util.FileUtils.pathToString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
@ -85,42 +86,44 @@ public class FileBasedConfigTest {
private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = " private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = "
+ ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL; + ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL;
private File trash; private Path trash;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
trash = File.createTempFile("tmp_", ""); trash = Files.createTempDirectory("tmp_");
trash.delete(); FS.getFileStoreAttributes(trash.getParent());
assertTrue("mkdir " + trash, trash.mkdir());
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); FileUtils.delete(trash.toFile(),
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING | FileUtils.RETRY);
} }
@Test @Test
public void testSystemEncoding() throws IOException, ConfigInvalidException { public void testSystemEncoding() throws IOException, ConfigInvalidException {
final File file = createFile(CONTENT1.getBytes(UTF_8)); final Path file = createFile(CONTENT1.getBytes(UTF_8));
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
config.setString(USER, null, NAME, BOB); config.setString(USER, null, NAME, BOB);
config.save(); config.save();
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file)); assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
} }
@Test @Test
public void testUTF8withoutBOM() throws IOException, ConfigInvalidException { public void testUTF8withoutBOM() throws IOException, ConfigInvalidException {
final File file = createFile(CONTENT1.getBytes(UTF_8)); final Path file = createFile(CONTENT1.getBytes(UTF_8));
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
config.setString(USER, null, NAME, BOB); config.setString(USER, null, NAME, BOB);
config.save(); config.save();
assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file)); assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
} }
@Test @Test
@ -131,8 +134,9 @@ public void testUTF8withBOM() throws IOException, ConfigInvalidException {
bos1.write(0xBF); bos1.write(0xBF);
bos1.write(CONTENT1.getBytes(UTF_8)); bos1.write(CONTENT1.getBytes(UTF_8));
final File file = createFile(bos1.toByteArray()); final Path file = createFile(bos1.toByteArray());
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
@ -144,7 +148,7 @@ public void testUTF8withBOM() throws IOException, ConfigInvalidException {
bos2.write(0xBB); bos2.write(0xBB);
bos2.write(0xBF); bos2.write(0xBF);
bos2.write(CONTENT2.getBytes(UTF_8)); bos2.write(CONTENT2.getBytes(UTF_8));
assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
} }
@Test @Test
@ -153,8 +157,9 @@ public void testLeadingWhitespaces() throws IOException, ConfigInvalidException
bos1.write(" \n\t".getBytes(UTF_8)); bos1.write(" \n\t".getBytes(UTF_8));
bos1.write(CONTENT1.getBytes(UTF_8)); bos1.write(CONTENT1.getBytes(UTF_8));
final File file = createFile(bos1.toByteArray()); final Path file = createFile(bos1.toByteArray());
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
@ -164,19 +169,20 @@ public void testLeadingWhitespaces() throws IOException, ConfigInvalidException
final ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
bos2.write(" \n\t".getBytes(UTF_8)); bos2.write(" \n\t".getBytes(UTF_8));
bos2.write(CONTENT2.getBytes(UTF_8)); bos2.write(CONTENT2.getBytes(UTF_8));
assertArrayEquals(bos2.toByteArray(), IO.readFully(file)); assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
} }
@Test @Test
public void testIncludeAbsolute() public void testIncludeAbsolute()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
final File includedFile = createFile(CONTENT1.getBytes(UTF_8)); final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("[include]\npath=".getBytes(UTF_8)); bos.write("[include]\npath=".getBytes(UTF_8));
bos.write(pathToString(includedFile).getBytes(UTF_8)); bos.write(pathToString(includedFile.toFile()).getBytes(UTF_8));
final File file = createFile(bos.toByteArray()); final Path file = createFile(bos.toByteArray());
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
} }
@ -184,13 +190,14 @@ public void testIncludeAbsolute()
@Test @Test
public void testIncludeRelativeDot() public void testIncludeRelativeDot()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("[include]\npath=".getBytes(UTF_8)); bos.write("[include]\npath=".getBytes(UTF_8));
bos.write(("./" + includedFile.getName()).getBytes(UTF_8)); bos.write(("./" + includedFile.getFileName()).getBytes(UTF_8));
final File file = createFile(bos.toByteArray(), "dir1"); final Path file = createFile(bos.toByteArray(), "dir1");
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
} }
@ -198,14 +205,15 @@ public void testIncludeRelativeDot()
@Test @Test
public void testIncludeRelativeDotDot() public void testIncludeRelativeDotDot()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1"); final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("[include]\npath=".getBytes(UTF_8)); bos.write("[include]\npath=".getBytes(UTF_8));
bos.write(("../" + includedFile.getParentFile().getName() + "/" bos.write(("../" + includedFile.getParent().getFileName() + "/"
+ includedFile.getName()).getBytes(UTF_8)); + includedFile.getFileName()).getBytes(UTF_8));
final File file = createFile(bos.toByteArray(), "dir2"); final Path file = createFile(bos.toByteArray(), "dir2");
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
} }
@ -213,13 +221,14 @@ public void testIncludeRelativeDotDot()
@Test @Test
public void testIncludeRelativeDotDotNotFound() public void testIncludeRelativeDotDotNotFound()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
final File includedFile = createFile(CONTENT1.getBytes(UTF_8)); final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("[include]\npath=".getBytes(UTF_8)); bos.write("[include]\npath=".getBytes(UTF_8));
bos.write(("../" + includedFile.getName()).getBytes(UTF_8)); bos.write(("../" + includedFile.getFileName()).getBytes(UTF_8));
final File file = createFile(bos.toByteArray()); final Path file = createFile(bos.toByteArray());
final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); final FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.load(); config.load();
assertEquals(null, config.getString(USER, null, NAME)); assertEquals(null, config.getString(USER, null, NAME));
} }
@ -227,16 +236,16 @@ public void testIncludeRelativeDotDotNotFound()
@Test @Test
public void testIncludeWithTilde() public void testIncludeWithTilde()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "home"); final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "home");
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("[include]\npath=".getBytes(UTF_8)); bos.write("[include]\npath=".getBytes(UTF_8));
bos.write(("~/" + includedFile.getName()).getBytes(UTF_8)); bos.write(("~/" + includedFile.getFileName()).getBytes(UTF_8));
final File file = createFile(bos.toByteArray(), "repo"); final Path file = createFile(bos.toByteArray(), "repo");
final FS fs = FS.DETECTED.newInstance(); final FS fs = FS.DETECTED.newInstance();
fs.setUserHome(includedFile.getParentFile()); fs.setUserHome(includedFile.getParent().toFile());
final FileBasedConfig config = new FileBasedConfig(file, fs); final FileBasedConfig config = new FileBasedConfig(file.toFile(), fs);
config.load(); config.load();
assertEquals(ALICE, config.getString(USER, null, NAME)); assertEquals(ALICE, config.getString(USER, null, NAME));
} }
@ -246,13 +255,14 @@ public void testIncludeDontInlineIncludedLinesOnSave()
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
// use a content with multiple sections and multiple key/value pairs // use a content with multiple sections and multiple key/value pairs
// because code for first line works different than for subsequent lines // because code for first line works different than for subsequent lines
final File includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1"); final Path includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1");
final File file = createFile(new byte[0], "dir2"); final Path file = createFile(new byte[0], "dir2");
FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED); FileBasedConfig config = new FileBasedConfig(file.toFile(),
FS.DETECTED);
config.setString("include", null, "path", config.setString("include", null, "path",
("../" + includedFile.getParentFile().getName() + "/" ("../" + includedFile.getParent().getFileName() + "/"
+ includedFile.getName())); + includedFile.getFileName()));
// just by setting the include.path, it won't be included // just by setting the include.path, it won't be included
assertEquals(null, config.getString(USER, null, NAME)); assertEquals(null, config.getString(USER, null, NAME));
@ -267,7 +277,7 @@ public void testIncludeDontInlineIncludedLinesOnSave()
assertEquals(2, assertEquals(2,
new StringTokenizer(expectedText, "\n", false).countTokens()); new StringTokenizer(expectedText, "\n", false).countTokens());
config = new FileBasedConfig(file, FS.DETECTED); config = new FileBasedConfig(file.toFile(), FS.DETECTED);
config.load(); config.load();
String actualText = config.toText(); String actualText = config.toText();
@ -285,16 +295,17 @@ public void testIncludeDontInlineIncludedLinesOnSave()
assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL)); assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL));
} }
private File createFile(byte[] content) throws IOException { private Path createFile(byte[] content) throws IOException {
return createFile(content, null); return createFile(content, null);
} }
private File createFile(byte[] content, String subdir) throws IOException { private Path createFile(byte[] content, String subdir) throws IOException {
File dir = subdir != null ? new File(trash, subdir) : trash; Path dir = subdir != null ? trash.resolve(subdir) : trash;
dir.mkdirs(); Files.createDirectories(dir);
File f = File.createTempFile(getClass().getName(), null, dir); Path f = Files.createTempFile(dir, getClass().getName(), null);
try (FileOutputStream os = new FileOutputStream(f, true)) { try (OutputStream os = Files.newOutputStream(f,
StandardOpenOption.APPEND)) {
os.write(content); os.write(content);
} }
return f; return f;

View File

@ -56,10 +56,13 @@
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.OpenSshConfig.Host; import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.junit.Before; import org.junit.Before;
@ -91,13 +94,19 @@ public void setUp() throws Exception {
} }
private void config(String data) throws IOException { private void config(String data) throws IOException {
long lastMtime = configFile.lastModified(); FS fs = FS.DETECTED;
long resolution = FS.getFileStoreAttributes(configFile.toPath())
.getFsTimestampResolution().toNanos();
Instant lastMtime = fs.lastModifiedInstant(configFile);
do { do {
try (final OutputStreamWriter fw = new OutputStreamWriter( try (final OutputStreamWriter fw = new OutputStreamWriter(
new FileOutputStream(configFile), UTF_8)) { new FileOutputStream(configFile), UTF_8)) {
fw.write(data); fw.write(data);
TimeUnit.NANOSECONDS.sleep(resolution);
} catch (InterruptedException e) {
Thread.interrupted();
} }
} while (lastMtime == configFile.lastModified()); } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
} }
@Test @Test

View File

@ -52,6 +52,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.time.Instant;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.ResetCommand.ResetType;
@ -86,7 +87,7 @@
public class FileTreeIteratorTest extends RepositoryTestCase { public class FileTreeIteratorTest extends RepositoryTestCase {
private final String[] paths = { "a,", "a,b", "a/b", "a0b" }; private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
private long[] mtime; private Instant[] mtime;
@Override @Override
@Before @Before
@ -99,11 +100,11 @@ public void setUp() throws Exception {
// This should stress the sorting code better than doing it in // This should stress the sorting code better than doing it in
// the correct order. // the correct order.
// //
mtime = new long[paths.length]; mtime = new Instant[paths.length];
for (int i = paths.length - 1; i >= 0; i--) { for (int i = paths.length - 1; i >= 0; i--) {
final String s = paths[i]; final String s = paths[i];
writeTrashFile(s, s); writeTrashFile(s, s);
mtime[i] = FS.DETECTED.lastModified(new File(trash, s)); mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s));
} }
} }
@ -199,7 +200,7 @@ public void testSimpleIterate() throws Exception {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[0], nameOf(top)); assertEquals(paths[0], nameOf(top));
assertEquals(paths[0].length(), top.getEntryLength()); assertEquals(paths[0].length(), top.getEntryLength());
assertEquals(mtime[0], top.getEntryLastModified()); assertEquals(mtime[0], top.getEntryLastModifiedInstant());
top.next(1); top.next(1);
assertFalse(top.first()); assertFalse(top.first());
@ -207,7 +208,7 @@ public void testSimpleIterate() throws Exception {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[1], nameOf(top)); assertEquals(paths[1], nameOf(top));
assertEquals(paths[1].length(), top.getEntryLength()); assertEquals(paths[1].length(), top.getEntryLength());
assertEquals(mtime[1], top.getEntryLastModified()); assertEquals(mtime[1], top.getEntryLastModifiedInstant());
top.next(1); top.next(1);
assertFalse(top.first()); assertFalse(top.first());
@ -222,7 +223,7 @@ public void testSimpleIterate() throws Exception {
assertFalse(sub.eof()); assertFalse(sub.eof());
assertEquals(paths[2], nameOf(sub)); assertEquals(paths[2], nameOf(sub));
assertEquals(paths[2].length(), subfti.getEntryLength()); assertEquals(paths[2].length(), subfti.getEntryLength());
assertEquals(mtime[2], subfti.getEntryLastModified()); assertEquals(mtime[2], subfti.getEntryLastModifiedInstant());
sub.next(1); sub.next(1);
assertTrue(sub.eof()); assertTrue(sub.eof());
@ -233,7 +234,7 @@ public void testSimpleIterate() throws Exception {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[3], nameOf(top)); assertEquals(paths[3], nameOf(top));
assertEquals(paths[3].length(), top.getEntryLength()); assertEquals(paths[3].length(), top.getEntryLength());
assertEquals(mtime[3], top.getEntryLastModified()); assertEquals(mtime[3], top.getEntryLastModifiedInstant());
top.next(1); top.next(1);
assertTrue(top.eof()); assertTrue(top.eof());
@ -345,20 +346,21 @@ public void testIsModifiedSymlinkAsFile() throws Exception {
@Test @Test
public void testIsModifiedFileSmudged() throws Exception { public void testIsModifiedFileSmudged() throws Exception {
File f = writeTrashFile("file", "content"); File f = writeTrashFile("file", "content");
FS fs = db.getFS();
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
// The idea of this test is to check the smudged handling // The idea of this test is to check the smudged handling
// Hopefully fsTick will make sure our entry gets smudged // Hopefully fsTick will make sure our entry gets smudged
fsTick(f); fsTick(f);
writeTrashFile("file", "content"); writeTrashFile("file", "content");
long lastModified = f.lastModified(); Instant lastModified = fs.lastModifiedInstant(f);
git.add().addFilepattern("file").call(); git.add().addFilepattern("file").call();
writeTrashFile("file", "conten2"); writeTrashFile("file", "conten2");
f.setLastModified(lastModified); fs.setLastModified(f.toPath(), lastModified);
// We cannot trust this to go fast enough on // We cannot trust this to go fast enough on
// a system with less than one-second lastModified // a system with less than one-second lastModified
// resolution, so we force the index to have the // resolution, so we force the index to have the
// same timestamp as the file we look at. // same timestamp as the file we look at.
db.getIndexFile().setLastModified(lastModified); fs.setLastModified(db.getIndexFile().toPath(), lastModified);
} }
DirCacheEntry dce = db.readDirCache().getEntry("file"); DirCacheEntry dce = db.readDirCache().getEntry("file");
FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db

View File

@ -1,109 +0,0 @@
/*
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
* 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.treewalk;
import java.io.File;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
/**
* A {@link FileTreeIterator} used in tests which allows to specify explicitly
* what will be returned by {@link #getEntryLastModified()}. This allows to
* write tests where certain files have to have the same modification time.
* <p>
* This iterator is configured by a list of strictly increasing long values
* t(0), t(1), ..., t(n). For each file with a modification between t(x) and
* t(x+1) [ t(x) &lt;= time &lt; t(x+1) ] this iterator will report t(x). For
* files with a modification time smaller t(0) a modification time of 0 is
* returned. For files with a modification time greater or equal t(n) t(n) will
* be returned.
* <p>
* This class was written especially to test racy-git problems
*/
public class FileTreeIteratorWithTimeControl extends FileTreeIterator {
private TreeSet<Long> modTimes;
public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo,
TreeSet<Long> modTimes) {
super(p, repo.getWorkTree(), repo.getFS());
this.modTimes = modTimes;
}
public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs,
TreeSet<Long> modTimes) {
super(p, f, fs);
this.modTimes = modTimes;
}
public FileTreeIteratorWithTimeControl(Repository repo,
TreeSet<Long> modTimes) {
super(repo);
this.modTimes = modTimes;
}
public FileTreeIteratorWithTimeControl(File f, FS fs,
TreeSet<Long> modTimes) {
super(f, fs, new Config().get(WorkingTreeOptions.KEY));
this.modTimes = modTimes;
}
@Override
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) {
return new FileTreeIteratorWithTimeControl(this,
((FileEntry) current()).getFile(), fs, modTimes);
}
@Override
public long getEntryLastModified() {
if (modTimes == null)
return 0;
Long cutOff = Long.valueOf(super.getEntryLastModified() + 1);
SortedSet<Long> head = modTimes.headSet(cutOff);
return head.isEmpty() ? 0 : head.last().longValue();
}
}

View File

@ -43,6 +43,7 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -65,6 +66,7 @@
import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.RepositoryCache;
import org.junit.After; import org.junit.After;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
@ -105,7 +107,7 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
String targetName = fs.readSymLink(link); String targetName = fs.readSymLink(link);
assertEquals("å", targetName); assertEquals("å", targetName);
assertTrue(fs.lastModified(link) > 0); assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));
assertEquals(2, fs.length(link)); assertEquals(2, fs.length(link));
@ -118,8 +120,9 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
// Now create the link target // Now create the link target
FileUtils.createNewFile(target); FileUtils.createNewFile(target);
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
assertTrue(fs.lastModified(link) > 0); assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
assertTrue(fs.lastModified(target) > fs.lastModified(link)); assertTrue(fs.lastModifiedInstant(target)
.compareTo(fs.lastModifiedInstant(link)) > 0);
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));
fs.setExecute(target, true); fs.setExecute(target, true);
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));
@ -200,7 +203,8 @@ public void testFsTimestampResolution() throws Exception {
.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH) .ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH)
.withZone(ZoneId.systemDefault()); .withZone(ZoneId.systemDefault());
Path dir = Files.createTempDirectory("probe-filesystem"); Path dir = Files.createTempDirectory("probe-filesystem");
Duration resolution = FS.getFsTimerResolution(dir); Duration resolution = FS.getFileStoreAttributes(dir)
.getFsTimestampResolution();
long resolutionNs = resolution.toNanos(); long resolutionNs = resolution.toNanos();
assertTrue(resolutionNs > 0); assertTrue(resolutionNs > 0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
@ -223,4 +227,11 @@ public void testFsTimestampResolution() throws Exception {
} }
} }
} }
// bug 548682
@Test
public void testRepoCacheRelativePathUnbornRepo() {
assertFalse(RepositoryCache.FileKey
.isGitRepository(new File("repo.git"), FS.DETECTED));
}
} }

View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class SimpleLruCacheTest {
private Path trash;
private SimpleLruCache<String, String> cache;
@Before
public void setup() throws IOException {
trash = Files.createTempDirectory("tmp_");
cache = new SimpleLruCache<>(100, 0.2f);
}
@Before
@After
public void tearDown() throws Exception {
FileUtils.delete(trash.toFile(),
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
}
@Test
public void testPutGet() {
cache.put("a", "A");
cache.put("z", "Z");
assertEquals("A", cache.get("a"));
assertEquals("Z", cache.get("z"));
}
@Test(expected = IllegalArgumentException.class)
public void testPurgeFactorTooLarge() {
cache.configure(5, 1.01f);
}
@Test(expected = IllegalArgumentException.class)
public void testPurgeFactorTooLarge2() {
cache.configure(5, 100);
}
@Test(expected = IllegalArgumentException.class)
public void testPurgeFactorTooSmall() {
cache.configure(5, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testPurgeFactorTooSmall2() {
cache.configure(5, -100);
}
@Test
public void testGetMissing() {
assertEquals(null, cache.get("a"));
}
@Test
public void testPurge() {
for (int i = 0; i < 101; i++) {
cache.put("a" + i, "a" + i);
}
assertEquals(80, cache.size());
assertNull(cache.get("a0"));
assertNull(cache.get("a20"));
assertNotNull(cache.get("a21"));
assertNotNull(cache.get("a99"));
}
@Test
public void testConfigure() {
for (int i = 0; i < 100; i++) {
cache.put("a" + i, "a" + i);
}
assertEquals(100, cache.size());
cache.configure(10, 0.3f);
assertEquals(7, cache.size());
assertNull(cache.get("a0"));
assertNull(cache.get("a92"));
assertNotNull(cache.get("a93"));
assertNotNull(cache.get("a99"));
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.eclipse.jgit.util.Stats;
import org.junit.Test;
public class StatsTest {
@Test
public void testStatsTrivial() {
Stats s = new Stats();
s.add(1);
s.add(1);
s.add(1);
assertEquals(3, s.count());
assertEquals(1.0, s.min(), 1E-6);
assertEquals(1.0, s.max(), 1E-6);
assertEquals(1.0, s.avg(), 1E-6);
assertEquals(0.0, s.var(), 1E-6);
assertEquals(0.0, s.stddev(), 1E-6);
}
@Test
public void testStats() {
Stats s = new Stats();
s.add(1);
s.add(2);
s.add(3);
s.add(4);
assertEquals(4, s.count());
assertEquals(1.0, s.min(), 1E-6);
assertEquals(4.0, s.max(), 1E-6);
assertEquals(2.5, s.avg(), 1E-6);
assertEquals(1.666667, s.var(), 1E-6);
assertEquals(1.290994, s.stddev(), 1E-6);
}
@Test
/**
* see
* https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example
*/
public void testStatsCancellationExample1() {
Stats s = new Stats();
s.add(1E8 + 4);
s.add(1E8 + 7);
s.add(1E8 + 13);
s.add(1E8 + 16);
assertEquals(4, s.count());
assertEquals(1E8 + 4, s.min(), 1E-6);
assertEquals(1E8 + 16, s.max(), 1E-6);
assertEquals(1E8 + 10, s.avg(), 1E-6);
assertEquals(30, s.var(), 1E-6);
assertEquals(5.477226, s.stddev(), 1E-6);
}
@Test
/**
* see
* https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example
*/
public void testStatsCancellationExample2() {
Stats s = new Stats();
s.add(1E9 + 4);
s.add(1E9 + 7);
s.add(1E9 + 13);
s.add(1E9 + 16);
assertEquals(4, s.count());
assertEquals(1E9 + 4, s.min(), 1E-6);
assertEquals(1E9 + 16, s.max(), 1E-6);
assertEquals(1E9 + 10, s.avg(), 1E-6);
assertEquals(30, s.var(), 1E-6);
assertEquals(5.477226, s.stddev(), 1E-6);
}
@Test
public void testNoValues() {
Stats s = new Stats();
assertTrue(Double.isNaN(s.var()));
assertTrue(Double.isNaN(s.stddev()));
assertTrue(Double.isNaN(s.avg()));
assertTrue(Double.isNaN(s.min()));
assertTrue(Double.isNaN(s.max()));
s.add(42.3);
assertTrue(Double.isNaN(s.var()));
assertTrue(Double.isNaN(s.stddev()));
assertEquals(42.3, s.avg(), 1E-6);
assertEquals(42.3, s.max(), 1E-6);
assertEquals(42.3, s.min(), 1E-6);
s.add(42.3);
assertEquals(0, s.var(), 1E-6);
assertEquals(0, s.stddev(), 1E-6);
}
}

View File

@ -8,6 +8,26 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="mightBeRacilyClean(Instant)"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="setLastModified(Instant)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException"> <resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
@ -22,6 +42,34 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="CONFIG_FILESYSTEM_SECTION"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="CONFIG_KEY_MIN_RACY_THRESHOLD"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="CONFIG_KEY_TIMESTAMP_RESOLUTION"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/storage/file/FileBasedConfig.java" type="org.eclipse.jgit.storage.file.FileBasedConfig">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="load(boolean)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig"> <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
<filter id="336658481"> <filter id="336658481">
<message_arguments> <message_arguments>
@ -92,6 +140,28 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getEntryLastModifiedInstant()"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry">
<filter id="336695337">
<message_arguments>
<message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS"> <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
@ -101,17 +171,73 @@
</filter> </filter>
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.2.3"/> <message_argument value="5.1.9"/>
<message_argument value="getFsTimerResolution(Path)"/> <message_argument value="getFileStoreAttributes(Path)"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="lastModifiedInstant(File)"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="lastModifiedInstant(Path)"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="setAsyncFileStoreAttributes(boolean)"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="setLastModified(Path, Instant)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$Attributes">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributes">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="FileStoreAttributes"/>
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils"> <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.2.3"/> <message_argument value="5.1.8"/>
<message_argument value="touch(Path)"/> <message_argument value="touch(Path)"/>
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache">
<filter id="1109393411">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="org.eclipse.jgit.util.SimpleLruCache"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats">
<filter id="1109393411">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="org.eclipse.jgit.util.Stats"/>
</message_arguments>
</filter>
</resource>
</component> </component>

View File

@ -86,7 +86,7 @@ Export-Package: org.eclipse.jgit.annotations;version="5.2.3",
org.eclipse.jgit.pgm", org.eclipse.jgit.pgm",
org.eclipse.jgit.internal.storage.reftree;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", org.eclipse.jgit.internal.storage.reftree;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
org.eclipse.jgit.internal.submodule;version="5.2.3";x-internal:=true, org.eclipse.jgit.internal.submodule;version="5.2.3";x-internal:=true,
org.eclipse.jgit.internal.transport.parser;version="5.2.3";x-friends:="org.eclipse.jgit.test", org.eclipse.jgit.internal.transport.parser;version="5.2.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server",
org.eclipse.jgit.internal.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache", org.eclipse.jgit.internal.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache",
org.eclipse.jgit.lib;version="5.2.3"; org.eclipse.jgit.lib;version="5.2.3";
uses:="org.eclipse.jgit.revwalk, uses:="org.eclipse.jgit.revwalk,

View File

@ -209,6 +209,7 @@
<plugin> <plugin>
<groupId>com.github.spotbugs</groupId> <groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId> <artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs-maven-plugin-version}</version>
<configuration> <configuration>
<excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile> <excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
</configuration> </configuration>

View File

@ -104,6 +104,7 @@ cannotReadObjectsPath=Cannot read {0}/{1}: {2}
cannotReadTree=Cannot read tree {0} cannotReadTree=Cannot read tree {0}
cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating. cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
cannotSaveConfig=Cannot save config file ''{0}''
cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit. cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit.
cannotStoreObjects=cannot store objects cannotStoreObjects=cannot store objects
cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID
@ -389,6 +390,7 @@ invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1}
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
invalidPurgeFactor=Invalid purgeFactor {0}, values have to be in range between 0 and 1
invalidRedirectLocation=Invalid redirect location {0} -> {1} invalidRedirectLocation=Invalid redirect location {0} -> {1}
invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}'' invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}''
invalidReflogRevision=Invalid reflog revision: {0} invalidReflogRevision=Invalid reflog revision: {0}
@ -558,8 +560,10 @@ pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
pushNotPermitted=push not permitted pushNotPermitted=push not permitted
pushOptionsNotSupported=Push options not supported; received {0} pushOptionsNotSupported=Push options not supported; received {0}
rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
readConfigFailed=Reading config file ''{0}'' failed
readerIsRequired=Reader is required readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
readLastModifiedFailed=Reading lastModified of {0} failed
readTimedOut=Read timed out after {0} ms readTimedOut=Read timed out after {0} ms
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes. receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes.
@ -680,6 +684,7 @@ theFactoryMustNotBeNull=The factory must not be null
threadInterruptedWhileRunning="Current thread interrupted while running {0}" threadInterruptedWhileRunning="Current thread interrupted while running {0}"
timeIsUncertain=Time is uncertain timeIsUncertain=Time is uncertain
timerAlreadyTerminated=Timer already terminated timerAlreadyTerminated=Timer already terminated
timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds
tooManyCommands=Too many commands tooManyCommands=Too many commands
tooManyFilters=Too many "filter" lines in request tooManyFilters=Too many "filter" lines in request
tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)?

View File

@ -50,6 +50,7 @@
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
@ -228,7 +229,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
if (GITLINK != mode) { if (GITLINK != mode) {
entry.setLength(f.getEntryLength()); entry.setLength(f.getEntryLength());
entry.setLastModified(f.getEntryLastModified()); entry.setLastModified(f.getEntryLastModifiedInstant());
long len = f.getEntryContentLength(); long len = f.getEntryContentLength();
// We read and filter the content multiple times. // We read and filter the content multiple times.
// f.getEntryContentLength() reads and filters the input and // f.getEntryContentLength() reads and filters the input and
@ -241,7 +242,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
} }
} else { } else {
entry.setLength(0); entry.setLength(0);
entry.setLastModified(0); entry.setLastModified(Instant.ofEpochSecond(0));
entry.setObjectId(f.getEntryObjectId()); entry.setObjectId(f.getEntryObjectId());
} }
builder.add(entry); builder.add(entry);

View File

@ -56,13 +56,18 @@
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@ -375,13 +380,15 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
MutableObjectId idBuf = new MutableObjectId(); MutableObjectId idBuf = new MutableObjectId();
ObjectReader reader = walk.getObjectReader(); ObjectReader reader = walk.getObjectReader();
walk.reset(rw.parseTree(tree)); RevObject o = rw.peel(rw.parseAny(tree));
if (!paths.isEmpty()) walk.reset(getTree(o));
if (!paths.isEmpty()) {
walk.setFilter(PathFilterGroup.createFromStrings(paths)); walk.setFilter(PathFilterGroup.createFromStrings(paths));
}
// Put base directory into archive // Put base directory into archive
if (pfx.endsWith("/")) { //$NON-NLS-1$ if (pfx.endsWith("/")) { //$NON-NLS-1$
fmt.putEntry(outa, tree, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$ fmt.putEntry(outa, o, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$
FileMode.TREE, null); FileMode.TREE, null);
} }
@ -392,17 +399,18 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
if (walk.isSubtree()) if (walk.isSubtree())
walk.enterSubtree(); walk.enterSubtree();
if (mode == FileMode.GITLINK) if (mode == FileMode.GITLINK) {
// TODO(jrn): Take a callback to recurse // TODO(jrn): Take a callback to recurse
// into submodules. // into submodules.
mode = FileMode.TREE; mode = FileMode.TREE;
}
if (mode == FileMode.TREE) { if (mode == FileMode.TREE) {
fmt.putEntry(outa, tree, name + "/", mode, null); //$NON-NLS-1$ fmt.putEntry(outa, o, name + "/", mode, null); //$NON-NLS-1$
continue; continue;
} }
walk.getObjectId(idBuf, 0); walk.getObjectId(idBuf, 0);
fmt.putEntry(outa, tree, name, mode, reader.open(idBuf)); fmt.putEntry(outa, o, name, mode, reader.open(idBuf));
} }
return out; return out;
} finally { } finally {
@ -534,4 +542,19 @@ public ArchiveCommand setPaths(String... paths) {
this.paths = Arrays.asList(paths); this.paths = Arrays.asList(paths);
return this; return this;
} }
private RevTree getTree(RevObject o)
throws IncorrectObjectTypeException {
final RevTree t;
if (o instanceof RevCommit) {
t = ((RevCommit) o).getTree();
} else if (!(o instanceof RevTree)) {
throw new IncorrectObjectTypeException(tree.toObjectId(),
Constants.TYPE_TREE);
} else {
t = (RevTree) o;
}
return t;
}
} }

View File

@ -392,7 +392,7 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
final DirCacheEntry dcEntry = new DirCacheEntry(path); final DirCacheEntry dcEntry = new DirCacheEntry(path);
long entryLength = fTree.getEntryLength(); long entryLength = fTree.getEntryLength();
dcEntry.setLength(entryLength); dcEntry.setLength(entryLength);
dcEntry.setLastModified(fTree.getEntryLastModified()); dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
boolean objectExists = (dcTree != null boolean objectExists = (dcTree != null

View File

@ -422,7 +422,7 @@ private void resetIndex(ObjectId commitTree) throws IOException {
DirCacheIterator.class); DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) { if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
entry.setLastModified(indexEntry.getLastModified()); entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength()); entry.setLength(indexEntry.getLength());
} }

View File

@ -332,7 +332,7 @@ private void resetIndex(RevTree tree) throws IOException {
DirCacheIterator.class); DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) { if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
entry.setLastModified(indexEntry.getLastModified()); entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength()); entry.setLength(indexEntry.getLength());
} }

View File

@ -300,7 +300,8 @@ public RevCommit call() throws GitAPIException {
final DirCacheEntry entry = new DirCacheEntry( final DirCacheEntry entry = new DirCacheEntry(
treeWalk.getRawPath()); treeWalk.getRawPath());
entry.setLength(wtIter.getEntryLength()); entry.setLength(wtIter.getEntryLength());
entry.setLastModified(wtIter.getEntryLastModified()); entry.setLastModified(
wtIter.getEntryLastModifiedInstant());
entry.setFileMode(wtIter.getEntryFileMode()); entry.setFileMode(wtIter.getEntryFileMode());
long contentLength = wtIter.getEntryContentLength(); long contentLength = wtIter.getEntryContentLength();
try (InputStream in = wtIter.openEntryStream()) { try (InputStream in = wtIter.openEntryStream()) {

View File

@ -58,6 +58,7 @@
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@ -497,8 +498,7 @@ else if (ver != 2)
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
snapshot = FileSnapshot.save(liveFile); snapshot = FileSnapshot.save(liveFile);
int smudge_s = (int) (snapshot.lastModified() / 1000); Instant smudge = snapshot.lastModifiedInstant();
int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
// Load the individual file entries. // Load the individual file entries.
// //
@ -507,8 +507,9 @@ else if (ver != 2)
sortedEntries = new DirCacheEntry[entryCnt]; sortedEntries = new DirCacheEntry[entryCnt];
final MutableInteger infoAt = new MutableInteger(); final MutableInteger infoAt = new MutableInteger();
for (int i = 0; i < entryCnt; i++) for (int i = 0; i < entryCnt; i++) {
sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge_s, smudge_ns); sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
}
// After the file entries are index extensions, and then a footer. // After the file entries are index extensions, and then a footer.
// //
@ -679,8 +680,8 @@ void writeTo(File dir, OutputStream os) throws IOException {
// so we use the current timestamp as a approximation. // so we use the current timestamp as a approximation.
myLock.createCommitSnapshot(); myLock.createCommitSnapshot();
snapshot = myLock.getCommitSnapshot(); snapshot = myLock.getCommitSnapshot();
smudge_s = (int) (snapshot.lastModified() / 1000); smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond());
smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; smudge_ns = snapshot.lastModifiedInstant().getNano();
} else { } else {
// Used in unit tests only // Used in unit tests only
smudge_ns = 0; smudge_ns = 0;
@ -1025,7 +1026,7 @@ private void updateSmudgedEntries() throws IOException {
DirCacheEntry entry = iIter.getDirCacheEntry(); DirCacheEntry entry = iIter.getDirCacheEntry();
if (entry.isSmudged() && iIter.idEqual(fIter)) { if (entry.isSmudged() && iIter.idEqual(fIter)) {
entry.setLength(fIter.getEntryLength()); entry.setLength(fIter.getEntryLength());
entry.setLastModified(fIter.getEntryLastModified()); entry.setLastModified(fIter.getEntryLastModifiedInstant());
} }
} }
} }

View File

@ -50,6 +50,7 @@
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -425,8 +426,10 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
// update the timestamp of the index with the one from the // update the timestamp of the index with the one from the
// file if not set, as we are sure to be in sync here. // file if not set, as we are sure to be in sync here.
DirCacheEntry entry = i.getDirCacheEntry(); DirCacheEntry entry = i.getDirCacheEntry();
if (entry.getLastModified() == 0) Instant mtime = entry.getLastModifiedInstant();
entry.setLastModified(f.getEntryLastModified()); if (mtime == null || mtime.equals(Instant.EPOCH)) {
entry.setLastModified(f.getEntryLastModifiedInstant());
}
keep(entry); keep(entry);
} }
} else } else
@ -638,7 +641,7 @@ private void checkoutGitlink(String path, DirCacheEntry entry)
File gitlinkDir = new File(repo.getWorkTree(), path); File gitlinkDir = new File(repo.getWorkTree(), path);
FileUtils.mkdirs(gitlinkDir, true); FileUtils.mkdirs(gitlinkDir, true);
FS fs = repo.getFS(); FS fs = repo.getFS();
entry.setLastModified(fs.lastModified(gitlinkDir)); entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
} }
private static ArrayList<String> filterOut(ArrayList<String> strings, private static ArrayList<String> filterOut(ArrayList<String> strings,
@ -1460,7 +1463,7 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
} }
fs.createSymLink(f, target); fs.createSymLink(f, target);
entry.setLength(bytes.length); entry.setLength(bytes.length);
entry.setLastModified(fs.lastModified(f)); entry.setLastModified(fs.lastModifiedInstant(f));
return; return;
} }
@ -1529,7 +1532,7 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
FileUtils.delete(tmpFile); FileUtils.delete(tmpFile);
} }
} }
entry.setLastModified(fs.lastModified(f)); entry.setLastModified(fs.lastModifiedInstant(f));
} }
// Run an external filter command // Run an external filter command

View File

@ -56,6 +56,7 @@
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
@ -145,8 +146,8 @@ public class DirCacheEntry {
private byte inCoreFlags; private byte inCoreFlags;
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
final InputStream in, final MessageDigest md, final int smudge_s, final InputStream in, final MessageDigest md, final Instant smudge)
final int smudge_ns) throws IOException { throws IOException {
info = sharedInfo; info = sharedInfo;
infoOffset = infoAt.value; infoOffset = infoAt.value;
@ -215,8 +216,9 @@ public class DirCacheEntry {
md.update(nullpad, 0, padLen); md.update(nullpad, 0, padLen);
} }
if (mightBeRacilyClean(smudge_s, smudge_ns)) if (mightBeRacilyClean(smudge)) {
smudgeRacilyClean(); smudgeRacilyClean();
}
} }
/** /**
@ -344,8 +346,29 @@ void write(OutputStream os) throws IOException {
* @param smudge_ns * @param smudge_ns
* nanoseconds component of the index's last modified time. * nanoseconds component of the index's last modified time.
* @return true if extra careful checks should be used. * @return true if extra careful checks should be used.
* @deprecated use {@link #mightBeRacilyClean(Instant)} instead
*/ */
@Deprecated
public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) { public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns));
}
/**
* Is it possible for this entry to be accidentally assumed clean?
* <p>
* The "racy git" problem happens when a work file can be updated faster
* than the filesystem records file modification timestamps. It is possible
* for an application to edit a work file, update the index, then edit it
* again before the filesystem will give the work file a new modification
* timestamp. This method tests to see if file was written out at the same
* time as the index.
*
* @param smudge
* index's last modified time.
* @return true if extra careful checks should be used.
* @since 5.1.9
*/
public final boolean mightBeRacilyClean(Instant smudge) {
// If the index has a modification time then it came from disk // If the index has a modification time then it came from disk
// and was not generated from scratch in memory. In such cases // and was not generated from scratch in memory. In such cases
// the entry is 'racily clean' if the entry's cached modification // the entry is 'racily clean' if the entry's cached modification
@ -355,8 +378,9 @@ public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
// //
final int base = infoOffset + P_MTIME; final int base = infoOffset + P_MTIME;
final int mtime = NB.decodeInt32(info, base); final int mtime = NB.decodeInt32(info, base);
if (smudge_s == mtime) if (smudge.getEpochSecond() == mtime) {
return smudge_ns <= NB.decodeInt32(info, base + 4); return smudge.getNano() <= NB.decodeInt32(info, base + 4);
}
return false; return false;
} }
@ -563,21 +587,50 @@ public void setCreationTime(long when) {
* *
* @return last modification time of this file, in milliseconds since the * @return last modification time of this file, in milliseconds since the
* Java epoch (midnight Jan 1, 1970 UTC). * Java epoch (midnight Jan 1, 1970 UTC).
* @deprecated use {@link #getLastModifiedInstant()} instead
*/ */
@Deprecated
public long getLastModified() { public long getLastModified() {
return decodeTS(P_MTIME); return decodeTS(P_MTIME);
} }
/**
* Get the cached last modification date of this file.
* <p>
* One of the indicators that the file has been modified by an application
* changing the working tree is if the last modification time for the file
* differs from the time stored in this entry.
*
* @return last modification time of this file.
* @since 5.1.9
*/
public Instant getLastModifiedInstant() {
return decodeTSInstant(P_MTIME);
}
/** /**
* Set the cached last modification date of this file, using milliseconds. * Set the cached last modification date of this file, using milliseconds.
* *
* @param when * @param when
* new cached modification date of the file, in milliseconds. * new cached modification date of the file, in milliseconds.
* @deprecated use {@link #setLastModified(Instant)} instead
*/ */
@Deprecated
public void setLastModified(long when) { public void setLastModified(long when) {
encodeTS(P_MTIME, when); encodeTS(P_MTIME, when);
} }
/**
* Set the cached last modification date of this file.
*
* @param when
* new cached modification date of the file.
* @since 5.1.9
*/
public void setLastModified(Instant when) {
encodeTS(P_MTIME, when);
}
/** /**
* Get the cached size (mod 4 GB) (in bytes) of this file. * Get the cached size (mod 4 GB) (in bytes) of this file.
* <p> * <p>
@ -692,7 +745,8 @@ public byte[] getRawPath() {
@SuppressWarnings("nls") @SuppressWarnings("nls")
@Override @Override
public String toString() { public String toString() {
return getFileMode() + " " + getLength() + " " + getLastModified() return getFileMode() + " " + getLength() + " "
+ getLastModifiedInstant()
+ " " + getObjectId() + " " + getStage() + " " + " " + getObjectId() + " " + getStage() + " "
+ getPathString() + "\n"; + getPathString() + "\n";
} }
@ -750,12 +804,25 @@ private long decodeTS(int pIdx) {
return 1000L * sec + ms; return 1000L * sec + ms;
} }
private Instant decodeTSInstant(int pIdx) {
final int base = infoOffset + pIdx;
final int sec = NB.decodeInt32(info, base);
final int nano = NB.decodeInt32(info, base + 4);
return Instant.ofEpochSecond(sec, nano);
}
private void encodeTS(int pIdx, long when) { private void encodeTS(int pIdx, long when) {
final int base = infoOffset + pIdx; final int base = infoOffset + pIdx;
NB.encodeInt32(info, base, (int) (when / 1000)); NB.encodeInt32(info, base, (int) (when / 1000));
NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
} }
private void encodeTS(int pIdx, Instant when) {
final int base = infoOffset + pIdx;
NB.encodeInt32(info, base, (int) when.getEpochSecond());
NB.encodeInt32(info, base + 4, when.getNano());
}
private int getExtendedFlags() { private int getExtendedFlags() {
if (isExtended()) if (isExtended())
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;

View File

@ -165,6 +165,7 @@ public static JGitText get() {
/***/ public String cannotReadTree; /***/ public String cannotReadTree;
/***/ public String cannotRebaseWithoutCurrentHead; /***/ public String cannotRebaseWithoutCurrentHead;
/***/ public String cannotResolveLocalTrackingRefForUpdating; /***/ public String cannotResolveLocalTrackingRefForUpdating;
/***/ public String cannotSaveConfig;
/***/ public String cannotSquashFixupWithoutPreviousCommit; /***/ public String cannotSquashFixupWithoutPreviousCommit;
/***/ public String cannotStoreObjects; /***/ public String cannotStoreObjects;
/***/ public String cannotResolveUniquelyAbbrevObjectId; /***/ public String cannotResolveUniquelyAbbrevObjectId;
@ -450,6 +451,7 @@ public static JGitText get() {
/***/ public String invalidPathPeriodAtEndWindows; /***/ public String invalidPathPeriodAtEndWindows;
/***/ public String invalidPathSpaceAtEndWindows; /***/ public String invalidPathSpaceAtEndWindows;
/***/ public String invalidPathReservedOnWindows; /***/ public String invalidPathReservedOnWindows;
/***/ public String invalidPurgeFactor;
/***/ public String invalidRedirectLocation; /***/ public String invalidRedirectLocation;
/***/ public String invalidRefAdvertisementLine; /***/ public String invalidRefAdvertisementLine;
/***/ public String invalidReflogRevision; /***/ public String invalidReflogRevision;
@ -619,8 +621,10 @@ public static JGitText get() {
/***/ public String pushNotPermitted; /***/ public String pushNotPermitted;
/***/ public String pushOptionsNotSupported; /***/ public String pushOptionsNotSupported;
/***/ public String rawLogMessageDoesNotParseAsLogEntry; /***/ public String rawLogMessageDoesNotParseAsLogEntry;
/***/ public String readConfigFailed;
/***/ public String readerIsRequired; /***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed; /***/ public String readingObjectsFromLocalRepositoryFailed;
/***/ public String readLastModifiedFailed;
/***/ public String readTimedOut; /***/ public String readTimedOut;
/***/ public String receivePackObjectTooLarge1; /***/ public String receivePackObjectTooLarge1;
/***/ public String receivePackObjectTooLarge2; /***/ public String receivePackObjectTooLarge2;
@ -736,6 +740,7 @@ public static JGitText get() {
/***/ public String tagAlreadyExists; /***/ public String tagAlreadyExists;
/***/ public String tagNameInvalid; /***/ public String tagNameInvalid;
/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported; /***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported;
/***/ public String timeoutMeasureFsTimestampResolution;
/***/ public String transactionAborted; /***/ public String transactionAborted;
/***/ public String theFactoryMustNotBeNull; /***/ public String theFactoryMustNotBeNull;
/***/ public String threadInterruptedWhileRunning; /***/ public String threadInterruptedWhileRunning;

View File

@ -43,19 +43,24 @@
package org.eclipse.jgit.internal.storage.file; package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_FILESTORE_ATTRIBUTES;
import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_TIMESTAMP_RESOLUTION;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration; import java.time.Duration;
import java.util.Date; import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.FileStoreAttributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Caches when a file was last read, making it possible to detect future edits. * Caches when a file was last read, making it possible to detect future edits.
@ -74,6 +79,8 @@
* file is less than 3 seconds ago. * file is less than 3 seconds ago.
*/ */
public class FileSnapshot { public class FileSnapshot {
private static final Logger LOG = LoggerFactory
.getLogger(FileSnapshot.class);
/** /**
* An unknown file size. * An unknown file size.
* *
@ -81,8 +88,14 @@ public class FileSnapshot {
*/ */
public static final long UNKNOWN_SIZE = -1; public static final long UNKNOWN_SIZE = -1;
private static final Instant UNKNOWN_TIME = Instant.ofEpochMilli(-1);
private static final Object MISSING_FILEKEY = new Object(); private static final Object MISSING_FILEKEY = new Object();
private static final DateTimeFormatter dateFmt = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
.withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
/** /**
* A FileSnapshot that is considered to always be modified. * A FileSnapshot that is considered to always be modified.
* <p> * <p>
@ -90,8 +103,8 @@ public class FileSnapshot {
* file, but only after {@link #isModified(File)} gets invoked. The returned * file, but only after {@link #isModified(File)} gets invoked. The returned
* snapshot contains only invalid status information. * snapshot contains only invalid status information.
*/ */
public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME,
UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
/** /**
* A FileSnapshot that is clean if the file does not exist. * A FileSnapshot that is clean if the file does not exist.
@ -100,8 +113,8 @@ public class FileSnapshot {
* file to be clean. {@link #isModified(File)} will return false if the file * file to be clean. {@link #isModified(File)} will return false if the file
* path does not exist. * path does not exist.
*/ */
public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0, public static final FileSnapshot MISSING_FILE = new FileSnapshot(
Duration.ZERO, MISSING_FILEKEY) { Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) {
@Override @Override
public boolean isModified(File path) { public boolean isModified(File path) {
return FS.DETECTED.exists(path); return FS.DETECTED.exists(path);
@ -122,6 +135,22 @@ public static FileSnapshot save(File path) {
return new FileSnapshot(path); return new FileSnapshot(path);
} }
/**
* Record a snapshot for a specific file path without using config file to
* get filesystem timestamp resolution.
* <p>
* This method should be invoked before the file is accessed. It is used by
* FileBasedConfig to avoid endless recursion.
*
* @param path
* the path to later remember. The path's current status
* information is saved.
* @return the snapshot.
*/
public static FileSnapshot saveNoConfig(File path) {
return new FileSnapshot(path, false);
}
private static Object getFileKey(BasicFileAttributes fileAttributes) { private static Object getFileKey(BasicFileAttributes fileAttributes) {
Object fileKey = fileAttributes.fileKey(); Object fileKey = fileAttributes.fileKey();
return fileKey == null ? MISSING_FILEKEY : fileKey; return fileKey == null ? MISSING_FILEKEY : fileKey;
@ -141,18 +170,41 @@ private static Object getFileKey(BasicFileAttributes fileAttributes) {
* @param modified * @param modified
* the last modification time of the file * the last modification time of the file
* @return the snapshot. * @return the snapshot.
* @deprecated use {@link #save(Instant)} instead.
*/ */
@Deprecated
public static FileSnapshot save(long modified) { public static FileSnapshot save(long modified) {
final long read = System.currentTimeMillis(); final Instant read = Instant.now();
return new FileSnapshot(read, modified, -1, Duration.ZERO, return new FileSnapshot(read, Instant.ofEpochMilli(modified),
MISSING_FILEKEY); UNKNOWN_SIZE, FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY);
}
/**
* Record a snapshot for a file for which the last modification time is
* already known.
* <p>
* This method should be invoked before the file is accessed.
* <p>
* Note that this method cannot rely on measuring file timestamp resolution
* to avoid racy git issues caused by finite file timestamp resolution since
* it's unknown in which filesystem the file is located. Hence the worst
* case fallback for timestamp resolution is used.
*
* @param modified
* the last modification time of the file
* @return the snapshot.
*/
public static FileSnapshot save(Instant modified) {
final Instant read = Instant.now();
return new FileSnapshot(read, modified, UNKNOWN_SIZE,
FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY);
} }
/** Last observed modification time of the path. */ /** Last observed modification time of the path. */
private final long lastModified; private final Instant lastModified;
/** Last wall-clock time the path was read. */ /** Last wall-clock time the path was read. */
private volatile long lastRead; private volatile Instant lastRead;
/** True once {@link #lastRead} is far later than {@link #lastModified}. */ /** True once {@link #lastRead} is far later than {@link #lastModified}. */
private boolean cannotBeRacilyClean; private boolean cannotBeRacilyClean;
@ -162,8 +214,8 @@ public static FileSnapshot save(long modified) {
* When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */
private final long size; private final long size;
/** measured filesystem timestamp resolution */ /** measured FileStore attributes */
private Duration fsTimestampResolution; private FileStoreAttributes fileStoreAttributeCache;
/** /**
* Object that uniquely identifies the given file, or {@code * Object that uniquely identifies the given file, or {@code
@ -171,31 +223,57 @@ public static FileSnapshot save(long modified) {
*/ */
private final Object fileKey; private final Object fileKey;
private final File file;
/** /**
* Record a snapshot for a specific file path. * Record a snapshot for a specific file path.
* <p> * <p>
* This method should be invoked before the file is accessed. * This method should be invoked before the file is accessed.
* *
* @param path * @param file
* the path to later remember. The path's current status * the path to remember meta data for. The path's current status
* information is saved. * information is saved.
*/ */
protected FileSnapshot(File path) { protected FileSnapshot(File file) {
this.lastRead = System.currentTimeMillis(); this(file, true);
this.fsTimestampResolution = FS }
.getFsTimerResolution(path.toPath().getParent());
/**
* Record a snapshot for a specific file path.
* <p>
* This method should be invoked before the file is accessed.
*
* @param file
* the path to remember meta data for. The path's current status
* information is saved.
* @param useConfig
* if {@code true} read filesystem time resolution from
* configuration file otherwise use fallback resolution
*/
protected FileSnapshot(File file, boolean useConfig) {
this.file = file;
this.lastRead = Instant.now();
this.fileStoreAttributeCache = useConfig
? FS.getFileStoreAttributes(file.toPath().getParent())
: FALLBACK_FILESTORE_ATTRIBUTES;
BasicFileAttributes fileAttributes = null; BasicFileAttributes fileAttributes = null;
try { try {
fileAttributes = FS.DETECTED.fileAttributes(path); fileAttributes = FS.DETECTED.fileAttributes(file);
} catch (IOException e) { } catch (IOException e) {
this.lastModified = path.lastModified(); this.lastModified = Instant.ofEpochMilli(file.lastModified());
this.size = path.length(); this.size = file.length();
this.fileKey = MISSING_FILEKEY; this.fileKey = MISSING_FILEKEY;
return; return;
} }
this.lastModified = fileAttributes.lastModifiedTime().toMillis(); this.lastModified = fileAttributes.lastModifiedTime().toInstant();
this.size = fileAttributes.size(); this.size = fileAttributes.size();
this.fileKey = getFileKey(fileAttributes); this.fileKey = getFileKey(fileAttributes);
if (LOG.isDebugEnabled()) {
LOG.debug("file={}, create new FileSnapshot: lastRead={}, lastModified={}, size={}, fileKey={}", //$NON-NLS-1$
file, dateFmt.format(lastRead),
dateFmt.format(lastModified), Long.valueOf(size),
fileKey.toString());
}
} }
private boolean sizeChanged; private boolean sizeChanged;
@ -206,11 +284,17 @@ protected FileSnapshot(File path) {
private boolean wasRacyClean; private boolean wasRacyClean;
private FileSnapshot(long read, long modified, long size, private long delta;
private long racyThreshold;
private FileSnapshot(Instant read, Instant modified, long size,
@NonNull Duration fsTimestampResolution, @NonNull Object fileKey) { @NonNull Duration fsTimestampResolution, @NonNull Object fileKey) {
this.file = null;
this.lastRead = read; this.lastRead = read;
this.lastModified = modified; this.lastModified = modified;
this.fsTimestampResolution = fsTimestampResolution; this.fileStoreAttributeCache = new FileStoreAttributes(
fsTimestampResolution);
this.size = size; this.size = size;
this.fileKey = fileKey; this.fileKey = fileKey;
} }
@ -219,8 +303,19 @@ private FileSnapshot(long read, long modified, long size,
* Get time of last snapshot update * Get time of last snapshot update
* *
* @return time of last snapshot update * @return time of last snapshot update
* @deprecated use {@link #lastModifiedInstant()} instead
*/ */
@Deprecated
public long lastModified() { public long lastModified() {
return lastModified.toEpochMilli();
}
/**
* Get time of last snapshot update
*
* @return time of last snapshot update
*/
public Instant lastModifiedInstant() {
return lastModified; return lastModified;
} }
@ -239,16 +334,16 @@ public long size() {
* @return true if the path needs to be read again. * @return true if the path needs to be read again.
*/ */
public boolean isModified(File path) { public boolean isModified(File path) {
long currLastModified; Instant currLastModified;
long currSize; long currSize;
Object currFileKey; Object currFileKey;
try { try {
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
currLastModified = fileAttributes.lastModifiedTime().toMillis(); currLastModified = fileAttributes.lastModifiedTime().toInstant();
currSize = fileAttributes.size(); currSize = fileAttributes.size();
currFileKey = getFileKey(fileAttributes); currFileKey = getFileKey(fileAttributes);
} catch (IOException e) { } catch (IOException e) {
currLastModified = path.lastModified(); currLastModified = Instant.ofEpochMilli(path.lastModified());
currSize = path.length(); currSize = path.length();
currFileKey = MISSING_FILEKEY; currFileKey = MISSING_FILEKEY;
} }
@ -290,7 +385,7 @@ public boolean isModified(File path) {
* the other snapshot. * the other snapshot.
*/ */
public void setClean(FileSnapshot other) { public void setClean(FileSnapshot other) {
final long now = other.lastRead; final Instant now = other.lastRead;
if (!isRacyClean(now)) { if (!isRacyClean(now)) {
cannotBeRacilyClean = true; cannotBeRacilyClean = true;
} }
@ -304,9 +399,10 @@ public void setClean(FileSnapshot other) {
* if sleep was interrupted * if sleep was interrupted
*/ */
public void waitUntilNotRacy() throws InterruptedException { public void waitUntilNotRacy() throws InterruptedException {
while (isRacyClean(System.currentTimeMillis())) { long timestampResolution = fileStoreAttributeCache
TimeUnit.NANOSECONDS .getFsTimestampResolution().toNanos();
.sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10); while (isRacyClean(Instant.now())) {
TimeUnit.NANOSECONDS.sleep(timestampResolution);
} }
} }
@ -318,7 +414,8 @@ public void waitUntilNotRacy() throws InterruptedException {
* @return true if the two snapshots share the same information. * @return true if the two snapshots share the same information.
*/ */
public boolean equals(FileSnapshot other) { public boolean equals(FileSnapshot other) {
return lastModified == other.lastModified && size == other.size boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size;
return lastModified.equals(other.lastModified) && sizeEq
&& Objects.equals(fileKey, other.fileKey); && Objects.equals(fileKey, other.fileKey);
} }
@ -341,8 +438,7 @@ public boolean equals(Object obj) {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size), return Objects.hash(lastModified, Long.valueOf(size), fileKey);
fileKey);
} }
/** /**
@ -377,6 +473,22 @@ boolean wasLastModifiedRacilyClean() {
return wasRacyClean; return wasRacyClean;
} }
/**
* @return the delta in nanoseconds between lastModified and lastRead during
* last racy check
*/
public long lastDelta() {
return delta;
}
/**
* @return the racyLimitNanos threshold in nanoseconds during last racy
* check
*/
public long lastRacyThreshold() {
return racyThreshold;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@SuppressWarnings("nls") @SuppressWarnings("nls")
@Override @Override
@ -387,24 +499,46 @@ public String toString() {
if (this == MISSING_FILE) { if (this == MISSING_FILE) {
return "MISSING_FILE"; return "MISSING_FILE";
} }
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", return "FileSnapshot[modified: " + dateFmt.format(lastModified)
Locale.US); + ", read: " + dateFmt.format(lastRead) + ", size:" + size
return "FileSnapshot[modified: " + f.format(new Date(lastModified))
+ ", read: " + f.format(new Date(lastRead)) + ", size:" + size
+ ", fileKey: " + fileKey + "]"; + ", fileKey: " + fileKey + "]";
} }
private boolean isRacyClean(long read) { private boolean isRacyClean(Instant read) {
// add a 10% safety margin racyThreshold = getEffectiveRacyThreshold();
long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10; delta = Duration.between(lastModified, read).toNanos();
return wasRacyClean = (read - lastModified) * 1_000_000 <= racyNanos; wasRacyClean = delta <= racyThreshold;
if (LOG.isDebugEnabled()) {
LOG.debug(
"file={}, isRacyClean={}, read={}, lastModified={}, delta={} ns, racy<={} ns", //$NON-NLS-1$
file, Boolean.valueOf(wasRacyClean), dateFmt.format(read),
dateFmt.format(lastModified), Long.valueOf(delta),
Long.valueOf(racyThreshold));
}
return wasRacyClean;
} }
private boolean isModified(long currLastModified) { private long getEffectiveRacyThreshold() {
long timestampResolution = fileStoreAttributeCache
.getFsTimestampResolution().toNanos();
long minRacyInterval = fileStoreAttributeCache.getMinimalRacyInterval()
.toNanos();
long max = Math.max(timestampResolution, minRacyInterval);
// safety margin: factor 2.5 below 100ms otherwise 1.25
return max < 100_000_000L ? max * 5 / 2 : max * 5 / 4;
}
private boolean isModified(Instant currLastModified) {
// Any difference indicates the path was modified. // Any difference indicates the path was modified.
lastModifiedChanged = lastModified != currLastModified; lastModifiedChanged = !lastModified.equals(currLastModified);
if (lastModifiedChanged) { if (lastModifiedChanged) {
if (LOG.isDebugEnabled()) {
LOG.debug(
"file={}, lastModified changed from {} to {}", //$NON-NLS-1$
file, dateFmt.format(lastModified),
dateFmt.format(currLastModified));
}
return true; return true;
} }
@ -412,26 +546,40 @@ private boolean isModified(long currLastModified) {
// after the last modification that any new modifications // after the last modification that any new modifications
// are certain to change the last modified time. // are certain to change the last modified time.
if (cannotBeRacilyClean) { if (cannotBeRacilyClean) {
LOG.debug("file={}, cannot be racily clean", file); //$NON-NLS-1$
return false; return false;
} }
if (!isRacyClean(lastRead)) { if (!isRacyClean(lastRead)) {
// Our last read should have marked cannotBeRacilyClean, // Our last read should have marked cannotBeRacilyClean,
// but this thread may not have seen the change. The read // but this thread may not have seen the change. The read
// of the volatile field lastRead should have fixed that. // of the volatile field lastRead should have fixed that.
LOG.debug("file={}, is unmodified", file); //$NON-NLS-1$
return false; return false;
} }
// We last read this path too close to its last observed // We last read this path too close to its last observed
// modification time. We may have missed a modification. // modification time. We may have missed a modification.
// Scan again, to ensure we still see the same state. // Scan again, to ensure we still see the same state.
LOG.debug("file={}, is racily clean", file); //$NON-NLS-1$
return true; return true;
} }
private boolean isFileKeyChanged(Object currFileKey) { private boolean isFileKeyChanged(Object currFileKey) {
return currFileKey != MISSING_FILEKEY && !currFileKey.equals(fileKey); boolean changed = currFileKey != MISSING_FILEKEY
&& !currFileKey.equals(fileKey);
if (changed) {
LOG.debug("file={}, FileKey changed from {} to {}", //$NON-NLS-1$
file, fileKey, currFileKey);
}
return changed;
} }
private boolean isSizeChanged(long currSize) { private boolean isSizeChanged(long currSize) {
return currSize != UNKNOWN_SIZE && currSize != size; boolean changed = (currSize != UNKNOWN_SIZE) && (currSize != size);
if (changed) {
LOG.debug("file={}, size changed from {} to {} bytes", //$NON-NLS-1$
file, Long.valueOf(size), Long.valueOf(currSize));
}
return changed;
} }
} }

View File

@ -372,8 +372,9 @@ private void deleteOldPacks(Collection<PackFile> oldPacks,
continue oldPackLoop; continue oldPackLoop;
if (!oldPack.shouldBeKept() if (!oldPack.shouldBeKept()
&& repo.getFS().lastModified( && repo.getFS()
oldPack.getPackFile()) < packExpireDate) { .lastModifiedInstant(oldPack.getPackFile())
.toEpochMilli() < packExpireDate) {
oldPack.close(); oldPack.close();
if (shouldLoosen) { if (shouldLoosen) {
loosen(inserter, reader, oldPack, ids); loosen(inserter, reader, oldPack, ids);
@ -561,8 +562,10 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
String fName = f.getName(); String fName = f.getName();
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
continue; continue;
if (repo.getFS().lastModified(f) >= expireDate) if (repo.getFS().lastModifiedInstant(f)
.toEpochMilli() >= expireDate) {
continue; continue;
}
try { try {
ObjectId id = ObjectId.fromString(d + fName); ObjectId id = ObjectId.fromString(d + fName);
if (objectsToKeep.contains(id)) if (objectsToKeep.contains(id))

View File

@ -56,8 +56,12 @@
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -422,9 +426,16 @@ public void setFSync(boolean on) {
public void waitForStatChange() throws InterruptedException { public void waitForStatChange() throws InterruptedException {
FileSnapshot o = FileSnapshot.save(ref); FileSnapshot o = FileSnapshot.save(ref);
FileSnapshot n = FileSnapshot.save(lck); FileSnapshot n = FileSnapshot.save(lck);
long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath())
.getFsTimestampResolution().toNanos();
while (o.equals(n)) { while (o.equals(n)) {
Thread.sleep(25 /* milliseconds */); TimeUnit.NANOSECONDS.sleep(fsTimeResolution);
lck.setLastModified(System.currentTimeMillis()); try {
Files.setLastModifiedTime(lck.toPath(),
FileTime.from(Instant.now()));
} catch (IOException e) {
n.waitUntilNotRacy();
}
n = FileSnapshot.save(lck); n = FileSnapshot.save(lck);
} }
} }
@ -474,11 +485,22 @@ private void saveStatInformation() {
* Get the modification time of the output file when it was committed. * Get the modification time of the output file when it was committed.
* *
* @return modification time of the lock file right before we committed it. * @return modification time of the lock file right before we committed it.
* @deprecated use {@link #getCommitLastModifiedInstant()} instead
*/ */
@Deprecated
public long getCommitLastModified() { public long getCommitLastModified() {
return commitSnapshot.lastModified(); return commitSnapshot.lastModified();
} }
/**
* Get the modification time of the output file when it was committed.
*
* @return modification time of the lock file right before we committed it.
*/
public Instant getCommitLastModifiedInstant() {
return commitSnapshot.lastModifiedInstant();
}
/** /**
* Get the {@link FileSnapshot} just before commit. * Get the {@link FileSnapshot} just before commit.
* *

View File

@ -60,6 +60,7 @@
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -107,7 +108,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
public static final Comparator<PackFile> SORT = new Comparator<PackFile>() { public static final Comparator<PackFile> SORT = new Comparator<PackFile>() {
@Override @Override
public int compare(PackFile a, PackFile b) { public int compare(PackFile a, PackFile b) {
return b.packLastModified - a.packLastModified; return b.packLastModified.compareTo(a.packLastModified);
} }
}; };
@ -132,7 +133,7 @@ public int compare(PackFile a, PackFile b) {
private int activeCopyRawData; private int activeCopyRawData;
int packLastModified; Instant packLastModified;
private PackFileSnapshot fileSnapshot; private PackFileSnapshot fileSnapshot;
@ -172,7 +173,7 @@ public int compare(PackFile a, PackFile b) {
public PackFile(File packFile, int extensions) { public PackFile(File packFile, int extensions) {
this.packFile = packFile; this.packFile = packFile;
this.fileSnapshot = PackFileSnapshot.save(packFile); this.fileSnapshot = PackFileSnapshot.save(packFile);
this.packLastModified = (int) (fileSnapshot.lastModified() >> 10); this.packLastModified = fileSnapshot.lastModifiedInstant();
this.extensions = extensions; this.extensions = extensions;
// Multiply by 31 here so we can more directly combine with another // Multiply by 31 here so we can more directly combine with another

View File

@ -50,6 +50,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -65,6 +66,7 @@
import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.fnmatch.FileNameMatcher;
import org.eclipse.jgit.transport.SshConstants; import org.eclipse.jgit.transport.SshConstants;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
@ -129,7 +131,7 @@ public class OpenSshConfigFile {
private final String localUserName; private final String localUserName;
/** Modification time of {@link #configFile} when it was last loaded. */ /** Modification time of {@link #configFile} when it was last loaded. */
private long lastModified; private Instant lastModified;
/** /**
* Encapsulates entries read out of the configuration file, and a cache of * Encapsulates entries read out of the configuration file, and a cache of
@ -223,8 +225,8 @@ private String toCacheKey(@NonNull String hostName, int port,
} }
private synchronized State refresh() { private synchronized State refresh() {
final long mtime = configFile.lastModified(); final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
if (mtime != lastModified) { if (!mtime.equals(lastModified)) {
State newState = new State(); State newState = new State();
try (BufferedReader br = Files try (BufferedReader br = Files
.newBufferedReader(configFile.toPath(), UTF_8)) { .newBufferedReader(configFile.toPath(), UTF_8)) {

View File

@ -476,4 +476,23 @@ public final class ConfigConstants {
* @since 5.2 * @since 5.2
*/ */
public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding"; public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding";
/**
* The "filesystem" section
* @since 5.1.9
*/
public static final String CONFIG_FILESYSTEM_SECTION = "filesystem";
/**
* The "timestampResolution" key
* @since 5.1.9
*/
public static final String CONFIG_KEY_TIMESTAMP_RESOLUTION = "timestampResolution";
/**
* The "minRacyThreshold" key
*
* @since 5.1.9
*/
public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold";
} }

View File

@ -226,6 +226,14 @@ public long getTimeUnit(Config config, String section, String subsection,
inputUnit = wantUnit; inputUnit = wantUnit;
inputMul = 1; inputMul = 1;
} else if (match(unitName, "ns", "nanoseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
inputUnit = TimeUnit.NANOSECONDS;
inputMul = 1;
} else if (match(unitName, "us", "microseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
inputUnit = TimeUnit.MICROSECONDS;
inputMul = 1;
} else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$ } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
inputUnit = TimeUnit.MILLISECONDS; inputUnit = TimeUnit.MILLISECONDS;
inputMul = 1; inputMul = 1;

View File

@ -47,6 +47,7 @@
package org.eclipse.jgit.merge; package org.eclipse.jgit.merge;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM; import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
@ -59,6 +60,7 @@
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -464,7 +466,7 @@ protected void cleanUp() throws NoWorkTreeException,
* @return the entry which was added to the index * @return the entry which was added to the index
*/ */
private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage, private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
long lastMod, long len) { Instant lastMod, long len) {
if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) { if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
DirCacheEntry e = new DirCacheEntry(path, stage); DirCacheEntry e = new DirCacheEntry(path, stage);
e.setFileMode(p.getEntryFileMode()); e.setFileMode(p.getEntryFileMode());
@ -491,7 +493,7 @@ private DirCacheEntry keep(DirCacheEntry e) {
e.getStage()); e.getStage());
newEntry.setFileMode(e.getFileMode()); newEntry.setFileMode(e.getFileMode());
newEntry.setObjectId(e.getObjectId()); newEntry.setObjectId(e.getObjectId());
newEntry.setLastModified(e.getLastModified()); newEntry.setLastModified(e.getLastModifiedInstant());
newEntry.setLength(e.getLength()); newEntry.setLength(e.getLength());
builder.add(newEntry); builder.add(newEntry);
return newEntry; return newEntry;
@ -667,16 +669,17 @@ protected boolean processEntry(CanonicalTreeParser base,
// we know about length and lastMod only after we have written the new content. // we know about length and lastMod only after we have written the new content.
// This will happen later. Set these values to 0 for know. // This will happen later. Set these values to 0 for know.
DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, 0, 0); DirCacheEntry.STAGE_0, EPOCH, 0);
addToCheckout(tw.getPathString(), e, attributes); addToCheckout(tw.getPathString(), e, attributes);
} }
return true; return true;
} else { } else {
// FileModes are not mergeable. We found a conflict on modes. // FileModes are not mergeable. We found a conflict on modes.
// For conflicting entries we don't know lastModified and length. // For conflicting entries we don't know lastModified and length.
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH,
0);
unmergedPaths.add(tw.getPathString()); unmergedPaths.add(tw.getPathString());
mergeResults.put( mergeResults.put(
tw.getPathString(), tw.getPathString(),
@ -708,7 +711,7 @@ protected boolean processEntry(CanonicalTreeParser base,
// the new content. // the new content.
// This will happen later. Set these values to 0 for know. // This will happen later. Set these values to 0 for know.
DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, 0, 0); DirCacheEntry.STAGE_0, EPOCH, 0);
if (e != null) { if (e != null) {
addToCheckout(tw.getPathString(), e, attributes); addToCheckout(tw.getPathString(), e, attributes);
} }
@ -737,16 +740,16 @@ protected boolean processEntry(CanonicalTreeParser base,
// detected later // detected later
if (nonTree(modeO) && !nonTree(modeT)) { if (nonTree(modeO) && !nonTree(modeT)) {
if (nonTree(modeB)) if (nonTree(modeB))
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
unmergedPaths.add(tw.getPathString()); unmergedPaths.add(tw.getPathString());
enterSubtree = false; enterSubtree = false;
return true; return true;
} }
if (nonTree(modeT) && !nonTree(modeO)) { if (nonTree(modeT) && !nonTree(modeO)) {
if (nonTree(modeB)) if (nonTree(modeB))
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
unmergedPaths.add(tw.getPathString()); unmergedPaths.add(tw.getPathString());
enterSubtree = false; enterSubtree = false;
return true; return true;
@ -773,9 +776,9 @@ protected boolean processEntry(CanonicalTreeParser base,
boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT); boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT);
// Don't attempt to resolve submodule link conflicts // Don't attempt to resolve submodule link conflicts
if (gitlinkConflict || !attributes.canBeContentMerged()) { if (gitlinkConflict || !attributes.canBeContentMerged()) {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
if (gitlinkConflict) { if (gitlinkConflict) {
MergeResult<SubmoduleConflict> result = new MergeResult<>( MergeResult<SubmoduleConflict> result = new MergeResult<>(
@ -822,10 +825,10 @@ protected boolean processEntry(CanonicalTreeParser base,
MergeResult<RawText> result = contentMerge(base, ours, theirs, MergeResult<RawText> result = contentMerge(base, ours, theirs,
attributes); attributes);
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_3, 0, 0); DirCacheEntry.STAGE_3, EPOCH, 0);
// OURS was deleted checkout THEIRS // OURS was deleted checkout THEIRS
if (modeO == 0) { if (modeO == 0) {
@ -957,9 +960,9 @@ private void updateIndex(CanonicalTreeParser base,
// A conflict occurred, the file will contain conflict markers // A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and the // the index will be populated with the three stages and the
// workdir (if used) contains the halfway merged content. // workdir (if used) contains the halfway merged content.
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
mergeResults.put(tw.getPathString(), result); mergeResults.put(tw.getPathString(), result);
return; return;
} }
@ -976,7 +979,7 @@ private void updateIndex(CanonicalTreeParser base,
? FileMode.REGULAR_FILE : FileMode.fromBits(newMode)); ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
if (mergedFile != null) { if (mergedFile != null) {
dce.setLastModified( dce.setLastModified(
nonNullRepo().getFS().lastModified(mergedFile)); nonNullRepo().getFS().lastModifiedInstant(mergedFile));
dce.setLength((int) mergedFile.length()); dce.setLength((int) mergedFile.length());
} }
dce.setObjectId(insertMergeResult(rawMerged, attributes)); dce.setObjectId(insertMergeResult(rawMerged, attributes));

View File

@ -148,11 +148,37 @@ public final File getFile() {
*/ */
@Override @Override
public void load() throws IOException, ConfigInvalidException { public void load() throws IOException, ConfigInvalidException {
load(true);
}
/**
* Load the configuration as a Git text style configuration file.
* <p>
* If the file does not exist, this configuration is cleared, and thus
* behaves the same as though the file exists, but is empty.
*
* @param useFileSnapshotWithConfig
* if {@code true} use the FileSnapshot with config, otherwise
* use it without config
* @throws IOException
* if IO failed
* @throws ConfigInvalidException
* if config is invalid
* @since 5.1.9
*/
public void load(boolean useFileSnapshotWithConfig)
throws IOException, ConfigInvalidException {
final int maxStaleRetries = 5; final int maxStaleRetries = 5;
int retries = 0; int retries = 0;
while (true) { while (true) {
final FileSnapshot oldSnapshot = snapshot; final FileSnapshot oldSnapshot = snapshot;
final FileSnapshot newSnapshot = FileSnapshot.save(getFile()); final FileSnapshot newSnapshot;
if (useFileSnapshotWithConfig) {
newSnapshot = FileSnapshot.save(getFile());
} else {
// don't use config in this snapshot to avoid endless recursion
newSnapshot = FileSnapshot.saveNoConfig(getFile());
}
try { try {
final byte[] in = IO.readFully(getFile()); final byte[] in = IO.readFully(getFile());
final ObjectId newHash = hash(in); final ObjectId newHash = hash(in);

View File

@ -49,6 +49,7 @@
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -126,7 +127,7 @@ boolean complete() {
private File netrc; private File netrc;
private long lastModified; private Instant lastModified;
private Map<String, NetRCEntry> hosts = new HashMap<>(); private Map<String, NetRCEntry> hosts = new HashMap<>();
@ -190,8 +191,10 @@ public NetRCEntry getEntry(String host) {
if (netrc == null) if (netrc == null)
return null; return null;
if (this.lastModified != this.netrc.lastModified()) if (!this.lastModified
.equals(FS.DETECTED.lastModifiedInstant(this.netrc))) {
parse(); parse();
}
NetRCEntry entry = this.hosts.get(host); NetRCEntry entry = this.hosts.get(host);
@ -212,7 +215,7 @@ public Collection<NetRCEntry> getEntries() {
private void parse() { private void parse() {
this.hosts.clear(); this.hosts.clear();
this.lastModified = this.netrc.lastModified(); this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
try (BufferedReader r = new BufferedReader( try (BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(netrc), UTF_8))) { new InputStreamReader(new FileInputStream(netrc), UTF_8))) {

View File

@ -53,6 +53,7 @@
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant;
import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -406,8 +407,14 @@ public long getLength() {
} }
@Override @Override
@Deprecated
public long getLastModified() { public long getLastModified() {
return attributes.getLastModifiedTime(); return attributes.getLastModifiedInstant().toEpochMilli();
}
@Override
public Instant getLastModifiedInstant() {
return attributes.getLastModifiedInstant();
} }
@Override @Override

View File

@ -59,6 +59,7 @@
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -645,11 +646,23 @@ public long getEntryContentLength() throws IOException {
* *
* @return last modified time of this file, in milliseconds since the epoch * @return last modified time of this file, in milliseconds since the epoch
* (Jan 1, 1970 UTC). * (Jan 1, 1970 UTC).
* @deprecated use {@link #getEntryLastModifiedInstant()} instead
*/ */
@Deprecated
public long getEntryLastModified() { public long getEntryLastModified() {
return current().getLastModified(); return current().getLastModified();
} }
/**
* Get the last modified time of this entry.
*
* @return last modified time of this file
* @since 5.1.9
*/
public Instant getEntryLastModifiedInstant() {
return current().getLastModifiedInstant();
}
/** /**
* Obtain an input stream to read the file content. * Obtain an input stream to read the file content.
* <p> * <p>
@ -924,30 +937,28 @@ public MetadataDiff compareMetadata(DirCacheEntry entry) {
// Git under windows only stores seconds so we round the timestamp // Git under windows only stores seconds so we round the timestamp
// Java gives us if it looks like the timestamp in index is seconds // Java gives us if it looks like the timestamp in index is seconds
// only. Otherwise we compare the timestamp at millisecond precision, // only. Otherwise we compare the timestamp at nanosecond precision,
// unless core.checkstat is set to "minimal", in which case we only // unless core.checkstat is set to "minimal", in which case we only
// compare the whole second part. // compare the whole second part.
long cacheLastModified = entry.getLastModified(); Instant cacheLastModified = entry.getLastModifiedInstant();
long fileLastModified = getEntryLastModified(); Instant fileLastModified = getEntryLastModifiedInstant();
long lastModifiedMillis = fileLastModified % 1000; if ((getOptions().getCheckStat() == CheckStat.MINIMAL)
long cacheMillis = cacheLastModified % 1000; || (cacheLastModified.getNano() == 0)
if (getOptions().getCheckStat() == CheckStat.MINIMAL) { // Some Java version on Linux return whole seconds only even
fileLastModified = fileLastModified - lastModifiedMillis; // when the file systems supports more precision.
cacheLastModified = cacheLastModified - cacheMillis; || (fileLastModified.getNano() == 0)) {
} else if (cacheMillis == 0) if (fileLastModified.getEpochSecond() != cacheLastModified
fileLastModified = fileLastModified - lastModifiedMillis; .getEpochSecond()) {
// Some Java version on Linux return whole seconds only even when return MetadataDiff.DIFFER_BY_TIMESTAMP;
// the file systems supports more precision. }
else if (lastModifiedMillis == 0) }
cacheLastModified = cacheLastModified - cacheMillis; if (!fileLastModified.equals(cacheLastModified)) {
if (fileLastModified != cacheLastModified)
return MetadataDiff.DIFFER_BY_TIMESTAMP; return MetadataDiff.DIFFER_BY_TIMESTAMP;
else if (!entry.isSmudged()) } else if (entry.isSmudged()) {
// The file is clean when you look at timestamps.
return MetadataDiff.EQUAL;
else
return MetadataDiff.SMUDGED; return MetadataDiff.SMUDGED;
}
// The file is clean when when comparing timestamps
return MetadataDiff.EQUAL;
} }
/** /**
@ -1274,9 +1285,25 @@ public String toString() {
* instance member instead. * instance member instead.
* *
* @return time since the epoch (in ms) of the last change. * @return time since the epoch (in ms) of the last change.
* @deprecated use {@link #getLastModifiedInstant()} instead
*/ */
@Deprecated
public abstract long getLastModified(); public abstract long getLastModified();
/**
* Get the last modified time of this entry.
* <p>
* <b>Note: Efficient implementation required.</b>
* <p>
* The implementation of this method must be efficient. If a subclass
* needs to compute the value they should cache the reference within an
* instance member instead.
*
* @return time of the last change.
* @since 5.1.9
*/
public abstract Instant getLastModifiedInstant();
/** /**
* Get the name of this entry within its directory. * Get the name of this entry within its directory.
* <p> * <p>

View File

@ -44,6 +44,7 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.EPOCH;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -53,7 +54,9 @@
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.Writer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.nio.file.FileStore; import java.nio.file.FileStore;
@ -65,27 +68,39 @@
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException; 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.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.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy; import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry; import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
@ -188,81 +203,478 @@ public int getRc() {
} }
} }
private static final class FileStoreAttributeCache { /**
* 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);
/** /**
* The last modified time granularity of FAT filesystems is 2 seconds. * Fallback filesystem timestamp resolution. The worst case timestamp
* resolution on FAT filesystems is 2 seconds.
*/ */
private static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
.ofMillis(2000); .ofMillis(2000);
private static final Map<FileStore, FileStoreAttributeCache> attributeCache = new ConcurrentHashMap<>(); /**
* 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);
static Duration getFsTimestampResolution(Path file) { 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.
*
* @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) {
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;
}
private static FileStoreAttributes getFileStoreAttributes(Path dir) {
FileStore s;
try { try {
Path dir = Files.isDirectory(file) ? file : file.getParent(); if (Files.exists(dir)) {
if (!dir.toFile().canWrite()) { s = Files.getFileStore(dir);
// can not determine FileStore of an unborn directory or in FileStoreAttributes c = attributeCache.get(s);
// a read-only directory if (c != null) {
return FALLBACK_TIMESTAMP_RESOLUTION; return c;
}
FileStore s = Files.getFileStore(dir);
FileStoreAttributeCache c = attributeCache.get(s);
if (c == null) {
c = new FileStoreAttributeCache(dir);
attributeCache.put(s, c);
if (LOG.isDebugEnabled()) {
LOG.debug(c.toString());
} }
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;
} }
return c.getFsTimestampResolution();
} catch (IOException | InterruptedException e) { CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
LOG.warn(e.getMessage(), e); .supplyAsync(() -> {
return FALLBACK_TIMESTAMP_RESOLUTION; 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.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 Duration fsTimestampResolution; private static String read(Path p) throws IOException {
final byte[] body = IO.readFully(p.toFile());
return new String(body, 0, body.length, UTF_8);
}
Duration getFsTimestampResolution() { 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) {
FileBasedConfig userConfig = SystemReader.getInstance()
.openUserConfig(null, FS.DETECTED);
try {
userConfig.load(false);
} catch (IOException e) {
LOG.error(MessageFormat.format(JGitText.get().readConfigFailed,
userConfig.getFile().getAbsolutePath()), e);
} catch (ConfigInvalidException e) {
LOG.error(MessageFormat.format(
JGitText.get().repositoryConfigFileInvalid,
userConfig.getFile().getAbsolutePath(),
e.getMessage()));
}
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) {
FileBasedConfig userConfig = SystemReader.getInstance()
.openUserConfig(null, FS.DETECTED);
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(false);
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 {
LOG.warn(MessageFormat.format(JGitText.get().cannotLock,
userConfig.getFile().getAbsolutePath()));
retries++;
Thread.sleep(20);
} catch (InterruptedException e1) {
Thread.interrupted();
}
} catch (IOException e) {
LOG.error(MessageFormat.format(
JGitText.get().cannotSaveConfig,
userConfig.getFile().getAbsolutePath()), e);
} catch (ConfigInvalidException e) {
LOG.error(MessageFormat.format(
JGitText.get().repositoryConfigFileInvalid,
userConfig.getFile().getAbsolutePath(),
e.getMessage()));
}
}
}
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;
/**
* @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; return fsTimestampResolution;
} }
private FileStoreAttributeCache(Path dir) /**
throws IOException, InterruptedException { * Construct a FileStoreAttributeCache entry for the given filesystem
Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ * timestamp resolution
Files.createFile(probe); *
try { * @param fsTimestampResolution
FileTime startTime = Files.getLastModifiedTime(probe); */
FileTime actTime = startTime; public FileStoreAttributes(
long sleepTime = 512; @NonNull Duration fsTimestampResolution) {
while (actTime.compareTo(startTime) <= 0) { this.fsTimestampResolution = fsTimestampResolution;
TimeUnit.NANOSECONDS.sleep(sleepTime); this.minimalRacyInterval = Duration.ZERO;
FileUtils.touch(probe);
actTime = Files.getLastModifiedTime(probe);
// limit sleep time to max. 100ms
if (sleepTime < 100_000_000L) {
sleepTime = sleepTime * 2;
}
}
fsTimestampResolution = Duration.between(startTime.toInstant(),
actTime.toInstant());
} catch (AccessDeniedException e) {
LOG.error(e.getLocalizedMessage(), e);
} finally {
Files.delete(probe);
}
} }
@SuppressWarnings("nls") @SuppressWarnings({ "nls", "boxing" })
@Override @Override
public String toString() { public String toString() {
return "FileStoreAttributeCache[" + attributeCache.keySet() return String.format(
.stream() "FileStoreAttributes[fsTimestampResolution=%,d µs, "
.map(key -> "FileStore[" + key + "]: fsTimestampResolution=" + "minimalRacyInterval=%,d µs]",
+ attributeCache.get(key).getFsTimestampResolution()) fsTimestampResolution.toNanos() / 1000,
.collect(Collectors.joining(",\n")) + "]"; 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. */
@ -279,6 +691,19 @@ public static FS detect() {
return detect(null); return detect(null);
} }
/**
* 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 * Auto-detect the appropriate file system abstraction, taking into account
* the presence of a Cygwin installation on the system. Using jgit in * the presence of a Cygwin installation on the system. Using jgit in
@ -307,18 +732,18 @@ public static FS detect(Boolean cygwinUsed) {
} }
/** /**
* Get an estimate for the filesystem timestamp resolution from a cache of * Get cached FileStore attributes, if not yet available measure them using
* timestamp resolution per FileStore, if not yet available it is measured * a probe file under the given directory.
* for a probe file under the given directory.
* *
* @param dir * @param dir
* the directory under which the probe file will be created to * the directory under which the probe file will be created to
* measure the timer resolution. * measure the timer resolution.
* @return measured filesystem timestamp resolution * @return measured filesystem timestamp resolution
* @since 5.2.3 * @since 5.1.9
*/ */
public static Duration getFsTimerResolution(@NonNull Path dir) { public static FileStoreAttributes getFileStoreAttributes(
return FileStoreAttributeCache.getFsTimestampResolution(dir); @NonNull Path dir) {
return FileStoreAttributes.get(dir);
} }
private volatile Holder<File> userHome; private volatile Holder<File> userHome;
@ -432,11 +857,41 @@ public boolean supportsSymlinks() {
* @return last modified time of f * @return last modified time of f
* @throws java.io.IOException * @throws java.io.IOException
* @since 3.0 * @since 3.0
* @deprecated use {@link #lastModifiedInstant(Path)} instead
*/ */
@Deprecated
public long lastModified(File f) throws IOException { public long lastModified(File f) throws IOException {
return FileUtils.lastModified(f); 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. If the OS/JRE support * Set the last modified time of a file system object. If the OS/JRE support
* symbolic links, the link is modified, not the target, * symbolic links, the link is modified, not the target,
@ -447,11 +902,28 @@ public long lastModified(File f) throws IOException {
* last modified time * last modified time
* @throws java.io.IOException * @throws java.io.IOException
* @since 3.0 * @since 3.0
* @deprecated use {@link #setLastModified(Path, Instant)} instead
*/ */
@Deprecated
public void setLastModified(File f, long time) throws IOException { public void setLastModified(File f, long time) throws IOException {
FileUtils.setLastModified(f, time); 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,
*
* @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 * 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. * it's the length of the link, else the length of the target.
@ -1526,9 +1998,19 @@ public long getCreationTime() {
/** /**
* @return the time (milliseconds since 1970-01-01) when this object was * @return the time (milliseconds since 1970-01-01) when this object was
* last modified * last modified
* @deprecated use getLastModifiedInstant instead
*/ */
@Deprecated
public long getLastModifiedTime() { public long getLastModifiedTime() {
return lastModifiedTime; 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 isDirectory;
@ -1539,7 +2021,7 @@ public long getLastModifiedTime() {
private final long creationTime; private final long creationTime;
private final long lastModifiedTime; private final Instant lastModifiedInstant;
private final boolean isExecutable; private final boolean isExecutable;
@ -1557,7 +2039,7 @@ public long getLastModifiedTime() {
Attributes(FS fs, File file, boolean exists, boolean isDirectory, Attributes(FS fs, File file, boolean exists, boolean isDirectory,
boolean isExecutable, boolean isSymbolicLink, boolean isExecutable, boolean isSymbolicLink,
boolean isRegularFile, long creationTime, boolean isRegularFile, long creationTime,
long lastModifiedTime, long length) { Instant lastModifiedInstant, long length) {
this.fs = fs; this.fs = fs;
this.file = file; this.file = file;
this.exists = exists; this.exists = exists;
@ -1566,7 +2048,7 @@ public long getLastModifiedTime() {
this.isSymbolicLink = isSymbolicLink; this.isSymbolicLink = isSymbolicLink;
this.isRegularFile = isRegularFile; this.isRegularFile = isRegularFile;
this.creationTime = creationTime; this.creationTime = creationTime;
this.lastModifiedTime = lastModifiedTime; this.lastModifiedInstant = lastModifiedInstant;
this.length = length; this.length = length;
} }
@ -1578,7 +2060,7 @@ public long getLastModifiedTime() {
* @param path * @param path
*/ */
public Attributes(File path, FS fs) { public Attributes(File path, FS fs) {
this(fs, path, false, false, false, false, false, 0L, 0L, 0L); this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
} }
/** /**
@ -1624,7 +2106,7 @@ public Attributes getAttributes(File path) {
boolean exists = isDirectory || isFile; boolean exists = isDirectory || isFile;
boolean canExecute = exists && !isDirectory && canExecute(path); boolean canExecute = exists && !isDirectory && canExecute(path);
boolean isSymlink = false; boolean isSymlink = false;
long lastModified = exists ? path.lastModified() : 0L; Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
long createTime = 0L; long createTime = 0L;
return new Attributes(this, path, exists, isDirectory, canExecute, return new Attributes(this, path, exists, isDirectory, canExecute,
isSymlink, isFile, createTime, lastModified, -1); isSymlink, isFile, createTime, lastModified, -1);

View File

@ -149,7 +149,7 @@ public FileVisitResult visitFile(Path file,
attrs.isSymbolicLink(), attrs.isSymbolicLink(),
attrs.isRegularFile(), attrs.isRegularFile(),
attrs.creationTime().toMillis(), attrs.creationTime().toMillis(),
attrs.lastModifiedTime().toMillis(), attrs.lastModifiedTime().toInstant(),
attrs.size()); attrs.size());
result.add(new FileEntry(f, fs, attributes, result.add(new FileEntry(f, fs, attributes,
fileModeStrategy)); fileModeStrategy));

View File

@ -49,7 +49,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.nio.channels.FileChannel;
import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption; import java.nio.file.CopyOption;
import java.nio.file.Files; import java.nio.file.Files;
@ -57,6 +57,7 @@
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
@ -66,6 +67,7 @@
import java.text.MessageFormat; import java.text.MessageFormat;
import java.text.Normalizer; import java.text.Normalizer;
import java.text.Normalizer.Form; import java.text.Normalizer.Form;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -74,11 +76,14 @@
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.FS.Attributes; import org.eclipse.jgit.util.FS.Attributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* File Utilities * File Utilities
*/ */
public class FileUtils { public class FileUtils {
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
/** /**
* Option to delete given {@code File} * Option to delete given {@code File}
@ -654,12 +659,31 @@ static boolean isSymlink(File file) {
* @return lastModified attribute for given file, not following symbolic * @return lastModified attribute for given file, not following symbolic
* links * links
* @throws IOException * @throws IOException
* @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
* FileTime
*/ */
@Deprecated
static long lastModified(File file) throws IOException { static long lastModified(File file) throws IOException {
return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
.toMillis(); .toMillis();
} }
/**
* @param path
* @return lastModified attribute for given file, not following symbolic
* links
*/
static Instant lastModifiedInstant(Path path) {
try {
return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
.toInstant();
} catch (IOException e) {
LOG.error(MessageFormat
.format(JGitText.get().readLastModifiedFailed, path));
return Instant.ofEpochMilli(path.toFile().lastModified());
}
}
/** /**
* Return all the attributes of a file, without following symbolic links. * Return all the attributes of a file, without following symbolic links.
* *
@ -678,10 +702,21 @@ static BasicFileAttributes fileAttributes(File file) throws IOException {
* @param time * @param time
* @throws IOException * @throws IOException
*/ */
@Deprecated
static void setLastModified(File file, long time) throws IOException { static void setLastModified(File file, long time) throws IOException {
Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
} }
/**
* @param path
* @param time
* @throws IOException
*/
static void setLastModified(Path path, Instant time)
throws IOException {
Files.setLastModifiedTime(path, FileTime.from(time));
}
/** /**
* @param file * @param file
* @return {@code true} if the given file exists, not following symbolic * @return {@code true} if the given file exists, not following symbolic
@ -786,7 +821,7 @@ static Attributes getFileAttributesBasic(FS fs, File file) {
readAttributes.isSymbolicLink(), readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), // readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), // readAttributes.creationTime().toMillis(), //
readAttributes.lastModifiedTime().toMillis(), readAttributes.lastModifiedTime().toInstant(),
readAttributes.isSymbolicLink() ? Constants readAttributes.isSymbolicLink() ? Constants
.encode(readSymLink(file)).length .encode(readSymLink(file)).length
: readAttributes.size()); : readAttributes.size());
@ -825,7 +860,7 @@ public static Attributes getFileAttributesPosix(FS fs, File file) {
readAttributes.isSymbolicLink(), readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), // readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), // readAttributes.creationTime().toMillis(), //
readAttributes.lastModifiedTime().toMillis(), readAttributes.lastModifiedTime().toInstant(),
readAttributes.size()); readAttributes.size());
return attributes; return attributes;
} catch (IOException e) { } catch (IOException e) {
@ -916,11 +951,13 @@ public static String pathToString(File file) {
* @param f * @param f
* the file to touch * the file to touch
* @throws IOException * @throws IOException
* @since 5.2.3 * @since 5.1.8
*/ */
public static void touch(Path f) throws IOException { public static void touch(Path f) throws IOException {
try (OutputStream fos = Files.newOutputStream(f)) { try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
// touch the file StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
// touch
} }
Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
} }
} }

View File

@ -0,0 +1,253 @@
/*
* Copyright (C) 2019, Marc Strapetz <marc.strapetz@syntevo.com>
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* 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 java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
/**
* Simple limited size cache based on ConcurrentHashMap purging entries in LRU
* order when reaching size limit
*
* @param <K>
* the type of keys maintained by this cache
* @param <V>
* the type of mapped values
*
* @since 5.1.9
*/
public class SimpleLruCache<K, V> {
private static class Entry<K, V> {
private final K key;
private final V value;
// pseudo clock timestamp of the last access to this entry
private volatile long lastAccessed;
private long lastAccessedSorting;
Entry(K key, V value, long lastAccessed) {
this.key = key;
this.value = value;
this.lastAccessed = lastAccessed;
}
void copyAccessTime() {
lastAccessedSorting = lastAccessed;
}
@SuppressWarnings("nls")
@Override
public String toString() {
return "Entry [lastAccessed=" + lastAccessed + ", key=" + key
+ ", value=" + value + "]";
}
}
private Lock lock = new ReentrantLock();
private Map<K, Entry<K,V>> map = new ConcurrentHashMap<>();
private volatile int maximumSize;
private int purgeSize;
// pseudo clock to implement LRU order of access to entries
private volatile long time = 0L;
private static void checkPurgeFactor(float purgeFactor) {
if (purgeFactor <= 0 || purgeFactor >= 1) {
throw new IllegalArgumentException(
MessageFormat.format(JGitText.get().invalidPurgeFactor,
Float.valueOf(purgeFactor)));
}
}
private static int purgeSize(int maxSize, float purgeFactor) {
return (int) ((1 - purgeFactor) * maxSize);
}
/**
* Create a new cache
*
* @param maxSize
* maximum size of the cache, to reduce need for synchronization
* this is not a hard limit. The real size of the cache could be
* slightly above this maximum if multiple threads put new values
* concurrently
* @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
*/
public SimpleLruCache(int maxSize, float purgeFactor) {
checkPurgeFactor(purgeFactor);
this.maximumSize = maxSize;
this.purgeSize = purgeSize(maxSize, purgeFactor);
}
/**
* Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
*
* <p>
* More formally, if this cache contains a mapping from a key {@code k} to a
* value {@code v} such that {@code key.equals(k)}, then this method returns
* {@code v}; otherwise it returns {@code null}. (There can be at most one
* such mapping.)
*
* @param key
* the key
*
* @throws NullPointerException
* if the specified key is null
*
* @return value mapped for this key, or {@code null} if no value is mapped
*/
public V get(Object key) {
Entry<K, V> entry = map.get(key);
if (entry != null) {
entry.lastAccessed = ++time;
return entry.value;
}
return null;
}
/**
* Maps the specified key to the specified value in this cache. Neither the
* key nor the value can be null.
*
* <p>
* The value can be retrieved by calling the {@code get} method with a key
* that is equal to the original key.
*
* @param key
* key with which the specified value is to be associated
* @param value
* value to be associated with the specified key
* @return the previous value associated with {@code key}, or {@code null}
* if there was no mapping for {@code key}
* @throws NullPointerException
* if the specified key or value is null
*/
public V put(@NonNull K key, @NonNull V value) {
map.put(key, new Entry<>(key, value, ++time));
if (map.size() > maximumSize) {
purge();
}
return value;
}
/**
* Returns the current size of this cache
*
* @return the number of key-value mappings in this cache
*/
public int size() {
return map.size();
}
/**
* Reconfigures the cache. If {@code maxSize} is reduced some entries will
* be purged.
*
* @param maxSize
* maximum size of the cache
*
* @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
*/
public void configure(int maxSize, float purgeFactor) {
lock.lock();
try {
checkPurgeFactor(purgeFactor);
this.maximumSize = maxSize;
this.purgeSize = purgeSize(maxSize, purgeFactor);
if (map.size() >= maximumSize) {
purge();
}
} finally {
lock.unlock();
}
}
private void purge() {
// don't try to compete if another thread already has the lock
if (lock.tryLock()) {
try {
List<Entry> entriesToPurge = new ArrayList<>(map.values());
// copy access times to avoid other threads interfere with
// sorting
for (Entry e : entriesToPurge) {
e.copyAccessTime();
}
Collections.sort(entriesToPurge,
Comparator.comparingLong(o -> -o.lastAccessedSorting));
for (int index = purgeSize; index < entriesToPurge
.size(); index++) {
map.remove(entriesToPurge.get(index).key);
}
} finally {
lock.unlock();
}
}
}
}

View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* 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;
/**
* Simple double statistics, computed incrementally, variance and standard
* deviation using Welford's online algorithm, see
* https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
*
* @since 5.1.9
*/
public class Stats {
private int n = 0;
private double avg = 0.0;
private double min = 0.0;
private double max = 0.0;
private double sum = 0.0;
/**
* Add a value
*
* @param x
* value
*/
public void add(double x) {
n++;
min = n == 1 ? x : Math.min(min, x);
max = n == 1 ? x : Math.max(max, x);
double d = x - avg;
avg += d / n;
sum += d * d * (n - 1) / n;
}
/**
* @return number of the added values
*/
public int count() {
return n;
}
/**
* @return minimum of the added values
*/
public double min() {
if (n < 1) {
return Double.NaN;
}
return min;
}
/**
* @return maximum of the added values
*/
public double max() {
if (n < 1) {
return Double.NaN;
}
return max;
}
/**
* @return average of the added values
*/
public double avg() {
if (n < 1) {
return Double.NaN;
}
return avg;
}
/**
* @return variance of the added values
*/
public double var() {
if (n < 2) {
return Double.NaN;
}
return sum / (n - 1);
}
/**
* @return standard deviation of the added values
*/
public double stddev() {
return Math.sqrt(this.var());
}
}

55
pom.xml
View File

@ -194,19 +194,20 @@
<osgi-core-version>4.3.1</osgi-core-version> <osgi-core-version>4.3.1</osgi-core-version>
<servlet-api-version>3.1.0</servlet-api-version> <servlet-api-version>3.1.0</servlet-api-version>
<jetty-version>9.4.11.v20180605</jetty-version> <jetty-version>9.4.11.v20180605</jetty-version>
<japicmp-version>0.13.0</japicmp-version> <japicmp-version>0.14.1</japicmp-version>
<httpclient-version>4.5.5</httpclient-version> <httpclient-version>4.5.5</httpclient-version>
<httpcore-version>4.4.9</httpcore-version> <httpcore-version>4.4.9</httpcore-version>
<slf4j-version>1.7.2</slf4j-version> <slf4j-version>1.7.2</slf4j-version>
<log4j-version>1.2.15</log4j-version> <log4j-version>1.2.15</log4j-version>
<maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version> <maven-javadoc-plugin-version>3.1.0</maven-javadoc-plugin-version>
<tycho-extras-version>1.2.0</tycho-extras-version> <tycho-extras-version>1.3.0</tycho-extras-version>
<gson-version>2.8.2</gson-version> <gson-version>2.8.2</gson-version>
<spotbugs-maven-plugin-version>3.1.8</spotbugs-maven-plugin-version>
<maven-surefire-version>2.22.1</maven-surefire-version>
<maven-compiler-plugin-version>3.8.0</maven-compiler-plugin-version>
<maven-project-info-reports-plugin-version>3.0.0</maven-project-info-reports-plugin-version> <maven-project-info-reports-plugin-version>3.0.0</maven-project-info-reports-plugin-version>
<maven-jxr-plugin-version>3.0.0</maven-jxr-plugin-version> <maven-jxr-plugin-version>3.0.0</maven-jxr-plugin-version>
<spotbugs-maven-plugin-version>3.1.12</spotbugs-maven-plugin-version>
<maven-surefire-plugin-version>3.0.0-M3</maven-surefire-plugin-version>
<maven-surefire-report-plugin-version>${maven-surefire-plugin-version}</maven-surefire-report-plugin-version>
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
<!-- Properties to enable jacoco code coverage analysis --> <!-- Properties to enable jacoco code coverage analysis -->
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin> <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@ -238,7 +239,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version> <version>3.1.2</version>
<configuration> <configuration>
<archive> <archive>
<manifestEntries> <manifestEntries>
@ -281,7 +282,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version> <version>3.1.0</version>
</plugin> </plugin>
<plugin> <plugin>
@ -293,7 +294,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-version}</version> <version>${maven-surefire-plugin-version}</version>
<configuration> <configuration>
<forkCount>${test-fork-count}</forkCount> <forkCount>${test-fork-count}</forkCount>
<reuseForks>true</reuseForks> <reuseForks>true</reuseForks>
@ -326,7 +327,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId> <artifactId>maven-pmd-plugin</artifactId>
<version>3.11.0</version> <version>3.12.0</version>
<configuration> <configuration>
<sourceEncoding>utf-8</sourceEncoding> <sourceEncoding>utf-8</sourceEncoding>
<minimumTokens>100</minimumTokens> <minimumTokens>100</minimumTokens>
@ -364,7 +365,7 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version> <version>0.8.4</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -381,7 +382,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId> <artifactId>maven-surefire-report-plugin</artifactId>
<version>${maven-surefire-version}</version> <version>${maven-surefire-report-plugin-version}</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -393,6 +394,26 @@
<artifactId>maven-project-info-reports-plugin</artifactId> <artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven-project-info-reports-plugin-version}</version> <version>${maven-project-info-reports-plugin-version}</version>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
@ -778,19 +799,19 @@
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac</artifactId> <artifactId>plexus-compiler-javac</artifactId>
<version>2.8.4</version> <version>2.8.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac-errorprone</artifactId> <artifactId>plexus-compiler-javac-errorprone</artifactId>
<version>2.8.4</version> <version>2.8.5</version>
</dependency> </dependency>
<!-- override plexus-compiler-javac-errorprone's dependency on <!-- override plexus-compiler-javac-errorprone's dependency on
Error Prone with the latest version --> Error Prone with the latest version -->
<dependency> <dependency>
<groupId>com.google.errorprone</groupId> <groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId> <artifactId>error_prone_core</artifactId>
<version>2.3.2</version> <version>2.3.3</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>
@ -823,12 +844,12 @@
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId> <artifactId>plexus-compiler-eclipse</artifactId>
<version>2.8.4</version> <version>2.8.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jdt</groupId> <groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId> <artifactId>ecj</artifactId>
<version>3.15.0</version> <version>3.17.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>